How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
Express · JavaScript

Fix NotFoundError: Cannot GET /api/users/profile in Express

This error occurs when no route matches the requested path and method in Express, resulting in a default 404 HTML response. This usually means the route is misspelled, uses the wrong HTTP method, or the router is not mounted at the correct path. Fix it by adding a catch-all 404 handler and verifying your route definitions.

Reading the Stack Trace

<!DOCTYPE html> <html lang="en"> <head><title>Error</title></head> <body><pre>Cannot GET /api/users/profile</pre></body> </html> // Express default 404 — no stack trace is generated. // The request falls through all middleware and route handlers: at /app/node_modules/express/lib/router/index.js:284:15 at Function.process_params (/app/node_modules/express/lib/router/index.js:346:12) at next (/app/node_modules/express/lib/router/index.js:280:10) at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5) at trim_prefix (/app/node_modules/express/lib/router/index.js:328:13) at /app/node_modules/express/lib/router/index.js:284:15

Here's what each line means:

Common Causes

1. Misspelled route path

The route is defined with a different path than what the client requests, causing no match.

const router = express.Router();

// Route defined as /user/profile (singular)
router.get('/user/profile', (req, res) => {
  res.json({ user: req.user });
});

// Client requests /users/profile (plural) — no match

2. Router not mounted at the correct prefix

The router is mounted at a different prefix than expected, so the full path does not match the client request.

const usersRouter = require('./routes/users');

// Mounted at /user instead of /api/users
app.use('/user', usersRouter);

// Client requests /api/users/profile — no match

3. No catch-all 404 handler

The application does not define a fallback handler for unmatched routes, so Express returns its default HTML error page.

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

// No 404 handler — Express sends default HTML error
app.listen(3000);

The Fix

Add a catch-all middleware after all route definitions that returns a JSON 404 response with the requested method and URL. This replaces Express's default HTML error page and helps API consumers understand what went wrong.

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

app.listen(3000);
After (fixed)
const usersRouter = express.Router();
usersRouter.get('/', (req, res) => res.json({ users: [] }));
usersRouter.get('/profile', (req, res) => res.json({ user: req.user }));

app.use('/api/users', usersRouter);

// Catch-all 404 handler — MUST be after all routes
app.use((req, res) => {
  res.status(404).json({
    error: 'Not Found',
    message: `Cannot ${req.method} ${req.originalUrl}`,
    availableEndpoints: ['/api/users', '/api/users/profile']
  });
});

app.listen(3000);

Testing the Fix

const request = require('supertest');
const express = require('express');

function createApp() {
  const app = express();
  const router = express.Router();
  router.get('/', (req, res) => res.json({ users: [] }));
  router.get('/profile', (req, res) => res.json({ user: { id: 1 } }));
  app.use('/api/users', router);
  app.use((req, res) => {
    res.status(404).json({ error: 'Not Found', message: `Cannot ${req.method} ${req.originalUrl}` });
  });
  return app;
}

describe('Route matching', () => {
  it('returns 200 for existing routes', async () => {
    const res = await request(createApp()).get('/api/users');
    expect(res.status).toBe(200);
  });

  it('returns 200 for /api/users/profile', async () => {
    const res = await request(createApp()).get('/api/users/profile');
    expect(res.status).toBe(200);
    expect(res.body.user.id).toBe(1);
  });

  it('returns JSON 404 for non-existent routes', async () => {
    const res = await request(createApp()).get('/api/nonexistent');
    expect(res.status).toBe(404);
    expect(res.body.error).toBe('Not Found');
    expect(res.body.message).toBe('Cannot GET /api/nonexistent');
  });
});

Run your tests:

npx jest --testPathPattern=route-not-found

Pushing Through CI/CD

git checkout -b fix/express-route-not-found,git add src/app.js src/routes/users.js src/__tests__/routeNotFound.test.js,git commit -m "fix: add JSON 404 catch-all handler for unmatched routes",git push origin fix/express-route-not-found

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npx jest --coverage
      - run: npm run lint

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

npm install bugstack-sdk

Step 2: Initialize

const { initBugStack } = require('bugstack-sdk')

initBugStack({ apiKey: process.env.BUGSTACK_API_KEY })

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run the test suite locally to confirm all routes resolve and unknown routes return JSON 404.
  2. Open a pull request with the route and 404 handler changes.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify route matching in staging before promoting to production.

Frequently Asked Questions

BugStack tests all registered routes for correct responses and verifies that unmatched routes return JSON 404s instead of HTML before marking it safe to deploy.

Every fix is delivered as a pull request with full CI validation. Your team reviews and approves before anything reaches production.

Express was originally designed for server-rendered web apps that serve HTML. For JSON APIs, you must add a custom 404 handler to return JSON responses instead of the default HTML error page.

The 404 catch-all handler should go after all routes but before any error-handling middleware (4-argument functions). This way unmatched routes get a 404, and errors from matched routes get proper error handling.