Skip to content

Fix: Express Cannot GET /route (404 Not Found)

FixDevs ·

Quick Answer

How to fix Express.js Cannot GET route 404 error caused by wrong route paths, missing middleware, route order issues, static files, and router mounting problems.

The Error

You visit a route in your Express app and get:

Cannot GET /api/users

Or:

<!DOCTYPE html>
<html>
<head><title>Error</title></head>
<body><pre>Cannot GET /api/users</pre></body>
</html>

With HTTP status 404 Not Found.

Or for POST requests:

Cannot POST /api/users

Express does not have a route handler registered for the requested URL and HTTP method. The request falls through all route handlers and Express responds with its default 404 message.

Why This Happens

Express matches incoming requests against registered routes in order. If no route matches the URL and HTTP method, Express returns “Cannot GET /path” (or “Cannot POST”, etc.).

Common causes:

  • Route path typo. /api/user registered but requesting /api/users.
  • Wrong HTTP method. Route uses app.post() but the request is a GET.
  • Route order issue. A catch-all route or middleware above intercepts the request.
  • Router not mounted. A router module is defined but never app.use()’d.
  • Missing leading slash. api/users instead of /api/users.
  • Case sensitivity. Express routes are case-insensitive by default, but path params are not.
  • Trailing slash mismatch. /api/users/ vs /api/users.

Fix 1: Check the Route Path

Verify the registered route matches exactly:

Broken — typo in route:

// Registered: /api/user (singular)
app.get('/api/user', (req, res) => {
  res.json({ users: [] });
});

// Request: /api/users (plural) → Cannot GET /api/users

Fixed:

app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

Debug — list all registered routes:

// Print all routes after setup
app._router.stack.forEach((r) => {
  if (r.route) {
    console.log(`${Object.keys(r.route.methods).join(',')} ${r.route.path}`);
  }
});

Or use express-list-endpoints:

npm install express-list-endpoints
const listEndpoints = require('express-list-endpoints');
console.log(listEndpoints(app));

Pro Tip: When you get “Cannot GET”, first check the exact URL you are requesting against the exact routes registered. A single character difference (trailing slash, plural vs singular, typo) causes a 404.

Fix 2: Fix Route Method Mismatch

The HTTP method must match:

// Only responds to POST
app.post('/api/users', (req, res) => {
  res.json({ created: true });
});

// GET /api/users → Cannot GET /api/users
// POST /api/users → { created: true }

Handle multiple methods:

app.route('/api/users')
  .get((req, res) => {
    res.json({ users: [] });
  })
  .post((req, res) => {
    res.json({ created: true });
  });

Or use app.all() for any method:

app.all('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

Fix 3: Fix Router Mounting

If you define routes in a separate router file, you must mount it:

Broken — router defined but not mounted:

// routes/users.js
const router = require('express').Router();

router.get('/', (req, res) => {
  res.json({ users: [] });
});

module.exports = router;

// app.js
const userRoutes = require('./routes/users');
// Forgot to mount! userRoutes is imported but never used

Fixed — mount the router:

// app.js
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);  // Mount at /api/users

Route prefix matters:

// routes/users.js
router.get('/', ...);       // Matches /api/users
router.get('/:id', ...);    // Matches /api/users/123
router.post('/', ...);      // Matches POST /api/users

// app.js
app.use('/api/users', userRoutes);  // Prefix: /api/users

The router’s paths are relative to the mount point. router.get('/') + app.use('/api/users', router) = /api/users.

Common Mistake: Defining the full path in both the router and the mount point. router.get('/api/users') mounted at app.use('/api', router) creates /api/api/users, not /api/users.

Fix 4: Fix Route Order

Express matches routes in the order they are defined. Earlier matches win:

Broken — catch-all before specific route:

// This catches everything!
app.get('*', (req, res) => {
  res.sendFile('index.html');
});

// This never runs
app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

Fixed — specific routes before catch-all:

// API routes first
app.get('/api/users', (req, res) => {
  res.json({ users: [] });
});

// Catch-all for SPA routing last
app.get('*', (req, res) => {
  res.sendFile('index.html');
});

Broken — static files middleware before routes:

app.use(express.static('public'));
// If public/api/users/index.html exists, it will be served instead of the route
app.get('/api/users', handler);

Fixed — mount API routes before static files:

app.use('/api', apiRouter);
app.use(express.static('public'));

Fix 5: Fix Static File Serving

If you are trying to serve static files (HTML, CSS, JS):

const path = require('path');

// Serve files from the 'public' directory
app.use(express.static(path.join(__dirname, 'public')));

// File at public/index.html → http://localhost:3000/index.html
// File at public/css/style.css → http://localhost:3000/css/style.css

For SPA (React, Vue, Angular) with client-side routing:

// API routes
app.use('/api', apiRouter);

// Static files
app.use(express.static(path.join(__dirname, 'build')));

// SPA fallback — must be LAST
app.get('*', (req, res) => {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

Fix 6: Fix Trailing Slash Issues

/api/users and /api/users/ are different routes by default:

app.get('/api/users', handler);
// GET /api/users → 200
// GET /api/users/ → 301 redirect to /api/users (default Express behavior)

Enable strict routing to treat them as different:

app.set('strict routing', true);
// Now /api/users and /api/users/ are different routes

Or handle both explicitly:

app.get('/api/users/?', handler);  // Regex: trailing slash optional

Fix 7: Fix Body Parsing Middleware

Missing body parser causes POST/PUT handlers to fail silently:

// Must be before route definitions!
app.use(express.json());                         // Parse JSON bodies
app.use(express.urlencoded({ extended: true }));  // Parse form bodies

// Now POST routes can access req.body
app.post('/api/users', (req, res) => {
  console.log(req.body);  // Without express.json(), this is undefined
  res.json({ created: true });
});

Fix 8: Add a Custom 404 Handler

Replace Express’s default “Cannot GET” response:

// Define all routes first...

// 404 handler — must be after all routes
app.use((req, res, next) => {
  res.status(404).json({
    error: 'Not Found',
    message: `Route ${req.method} ${req.url} not found`,
    timestamp: new Date().toISOString(),
  });
});

// Error handler — must be after 404 handler
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({
    error: 'Internal Server Error',
    message: err.message,
  });
});

Still Not Working?

Check that the server is running on the expected port:

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});

Check for proxy issues. If using Nginx or a reverse proxy, the proxy path might not match:

# Nginx strips /api prefix if not configured correctly
location /api/ {
    proxy_pass http://localhost:3000/api/;  # Include trailing slash on both
}

Check for CORS preflight failures. OPTIONS requests return 404 if not handled:

const cors = require('cors');
app.use(cors());  // Handles OPTIONS preflight requests

For Node.js module errors, see Fix: Node.js ERR_MODULE_NOT_FOUND. For port already in use, see Fix: Port 3000 already in use. For CORS errors, see Fix: CORS Access-Control-Allow-Origin error.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles