The app get is not a function error means your app variable isn’t an Express app instance, so route methods like get() don’t exist.
This error shows up when you expect app to be an Express application, but it’s actually something else, like a plain object, a function, a Router, a promise, or an empty export. Once that happens, calling app.get() throws a TypeError and your server stops booting.
The fix is almost always a chain of checks. You confirm what app is, trace where it came from, then correct one file. That file creates the Express instance or exports it.
What App Get Means In Node And Express Code
In Express, app is the value returned by calling express(). That object has HTTP method helpers like get(), post(), and use(). Those helpers register route handlers and middleware.
If you never created the app with express(), or you replaced the variable later, then app won’t have those methods. The error message is blunt, but it’s also a clue. It’s not complaining about your route path. It’s complaining about the thing you’re calling.
If you want a fast sanity check, log the type right before the failing line. When you see anything other than “function” methods on an Express app, you’ve found the direction to take next.
- Log The Value — Add
console.log(app)once, right above the firstapp.get()call. - Log The Properties — Run
console.log(Object.getOwnPropertyNames(app || {}))to see what you actually imported.
Run one test. Print typeof app, then check for listen and use. An Express app has both. A Router lacks listen, and a plain object lacks them all too in most setups today.
If those logs show an empty object, a Router, or something unexpected, jump to the section that matches your setup. The table below can narrow it in seconds.
App Get Is Not A Function In Express Apps With Common Causes
Most cases fall into a small set of patterns. The theme is the same each time. The file that calls app.get() is not holding the Express app you think it is.
| What You See | Likely Reason | Fix To Try |
|---|---|---|
| app is { } | Wrong export or import path | Export the Express instance, then import it without destructuring |
| app is a Router | You imported a router module | Use router.get() inside the router file, then mount it with app.use() |
| Error starts after refactor | Circular require between files | Break the loop by injecting app into the module, or move routes into a router |
| Works in one file, fails in another | Variable shadowing or reassignment | Rename local variables and stop reusing app for other values |
| ESM import looks right, still fails | Mixing CommonJS and ESM exports | Match export default with import app from, or match module.exports with require() |
Quick Checks That Solve Most Cases
Start with the basics. These catch the slip-ups that sneak in during copy-paste or a rushed refactor.
Make Sure You Created The Express App
Look for the line that creates the application. In CommonJS, it should look like const app = express(). In ESM, it will still call express(), but the import line changes.
- Check The Create Line — Confirm you have
const app = express()in the file that owns your server setup. - Check The Require Line — Confirm
const express = require('express')(or an ESM import) points to the package, not a local file named express. - Check The App Variable — Confirm nothing reassigns
appafter creation.
Confirm You Didn’t Destructure The Wrong Thing
A classic bug is importing with braces when the module exported a default value, or importing a default value when the module exported named values. The result is often an empty object or the wrong property.
- Match Default Export — If the app file uses
module.exports = app, import withconst app = require('./app'). - Match Named Export — If the app file exports
{ app }, import withconst { app } = require('./app'). - Print The Import — Log the imported value right after the import line to confirm it’s the Express app.
Check That You Didn’t Import A Promise
Some setups build the app after async work, like reading config or connecting to a database. If you export the pending promise and import it as app, you’ll end up calling get() on a promise object.
- Search For Async Exports — Look for
async function createApp()patterns in your app module. - Await Before Use — If the module returns a promise, await it in the entry file before registering routes.
- Keep Routes Sync — Register routes after the app exists, even if the server waits to listen.
Module Export Mixups That Trigger The Error
If quick checks didn’t fix it, the next likely culprit is how the app is exported and imported. Node can run CommonJS, ESM, or a mix that looks okay until runtime. Small differences in export style change what you get back at import time.
CommonJS Pattern That Works Reliably
Use a single module that creates the app and exports it. Then keep the file that calls listen() small and boring.
// app.js
const express = require('express')
const app = express()
app.get('/', (req, res) => {
res.send('OK')
})
module.exports = app
// server.js
const app = require('./app')
app.listen(3000)
Adjust the import to match your export. app must be the object created by express().
ESM Pattern That Stays Consistent
If your package.json sets "type" to "module", align your exports and imports so the default export stays a default export.
// app.js
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.send('OK')
})
export default app
// server.js
import app from './app.js'
app.listen(3000)
A mismatch here can yield undefined or an object with a default property. If your logs show a wrapper object, your import line is off by one layer.
Circular Imports That Leave You With A Half-Built Export
Circular imports are sneaky. File A imports File B, and File B imports File A. Node will hand one of them a partially built export so it can keep going. That partial export is often a plain object without methods, so app.get() breaks.
- Search For Two-Way Imports — Look for A requiring B while B also requires A.
- Move Routes Out — Put route handlers in a router module that does not import the server entry file.
- Inject The App — Export a function that accepts
app, then call it after creating the app.
Routing Setup Mistakes That Look Like App Problems
Sometimes your app is fine, but the file you’re editing is not the place to call app.get(). This happens when you switch to routers and forget that routers use router.get(), not app.get().
Use Router Files For Groups Of Routes
A router is created with express.Router(). It has the same method helpers, but it’s not the app. You attach it to the app with app.use().
// routes/users.js
const express = require('express')
const router = express.Router()
router.get('/', (req, res) => {
res.send('users')
})
module.exports = router
// app.js
const express = require('express')
const app = express()
const users = require('./routes/users')
app.use('/users', users)
module.exports = app
If you imported the router module and named it app, you’ll see the error right away. Rename the variable to what it is, and the bug often fixes itself.
Mount Order And File Ownership
Keep a single “owner” file for app creation. Put route groups in routers. Put server start logic in one entry file. When the roles mix, accidental imports show up and you end up calling route methods on the wrong thing.
- Create The App Once — Make one file responsible for
express()and middleware. - Register Routes Next — Mount routers after middleware like JSON parsing.
- Start Listening Last — Call
listen()from the entry file, not from router modules.
Harder Cases When App Gets Replaced At Runtime
If the error appears only after certain code runs, your app variable may be getting replaced. That can happen through reassignment, a bad return value, or a pattern that overwrites properties on the app object.
Watch For Reassignment And Shadowing
Search your codebase for app =. In a healthy Express setup, you should see the assignment once, right after calling express(). If you see more, track each one. A single stray reassignment can turn the app into a config object or a result from another function call.
- Rename Confusing Variables — Use names like
server,client, orapifor non-Express values. - Avoid Passing App As Generic Data — Don’t reuse
appas an argument name in unrelated functions. - Freeze The Reference — Use
constfor the app and don’t redeclare it in inner scopes.
Check For Runtime Patching Gone Wrong
Some plugins or custom wrappers extend the app object. If code assigns app.get to a non-function value, the error will read the same. This is rare, but the fix is straightforward. Locate the assignment and remove it.
- Search For app.get Assignments — Look for
app.get =orapp['get'] =. - Keep Settings Separate — Store config in a separate object instead of on the app instance.
- Restart After Changes — A stale dev server can hide a fixed bug; restart to confirm the new code is running.
A Minimal Baseline To Compare Your Project Against
When you’re stuck, compare your project against a tiny working server. If the tiny server runs and your project fails, the difference points at the broken link in your imports or app creation chain.
const express = require('express')
const app = express()
app.use(express.json())
app.get('/health', (req, res) => {
res.json({ status: 'ok' })
})
app.listen(3000, () => {
console.log('listening on 3000')
})
Now transfer pieces from your project into this baseline one at a time. Move middleware first, routers next, database wiring last. The moment the error returns, the last piece you moved is the one handing you the wrong app value.
If you want official references, the Express API and Node module docs are the two pages worth bookmarking.
- Open The Express API — https://expressjs.com/en/api.html
- Open Node Module Docs — https://nodejs.org/api/modules.html
Once the app variable points at a real Express instance again, the error goes away and your routes register normally. If you still see failures after that, the next error message will usually be a normal route or middleware bug, and that’s a nicer problem to have.
Treat app creation, route registration, and server startup as separate jobs. When each job lives in its own file, the app get is not a function error has fewer places to hide.
