express-server

star 9

Express server patterns with TypeScript, InversifyJS, and Socket.IO. Use when creating or modifying HTTP endpoints, middleware, route handlers, CORS config, graceful shutdown, or writing supertest integration tests.

recca0120 By recca0120 schedule Updated 4/19/2026

name: express-server description: > Express server patterns with TypeScript, InversifyJS, and Socket.IO. Use when creating or modifying HTTP endpoints, middleware, route handlers, CORS config, graceful shutdown, or writing supertest integration tests.

Express Server Skill

Stack: Express 4.x, TypeScript, ESM ("type": "module"), InversifyJS DI, Socket.IO, cors, helmet. The server is a thin HTTP layer — business logic lives in injected services, not in route files.


Project Structure

src/
  server/
    app.ts           # Express factory — creates and configures app, no listen()
    server.ts        # Entry point — binds port, handles SIGTERM
    routes/          # One file per resource, returns express.Router
    middleware/      # Custom middleware (auth, logging, validation)
    errors/          # AppError class, error handler middleware

Middleware Ordering

Register in this exact order — order is critical:

app.use(helmet());                    // 1. Security headers — first
app.use(cors(corsOptions));           // 2. CORS — before body parsing
app.use(express.json({ limit: '1mb' })); // 3. Body parsers
app.use(rateLimiter);                 // 4. Rate limiting
app.use('/static', express.static(staticDir, { maxAge: '1d' })); // 5. Static
app.use('/health', healthRouter);     // 6. Health — no auth needed
app.use('/api', apiRouter);           // 7. App routes
app.use(errorHandler);                // 8. Error handler — always last

Async Error Handling (Express 4)

Express 4 does not catch async errors automatically. Wrap handlers or use express-async-errors.

// Option A: wrapper (no extra dep)
const asyncHandler =
  (fn: RequestHandler): RequestHandler =>
  (req, res, next) =>
    Promise.resolve(fn(req, res, next)).catch(next);

// Option B: import once in app.ts (patches express globally)
import 'express-async-errors';

Error middleware — must have exactly four parameters:

app.use((err: unknown, req: Request, res: Response, _next: NextFunction) => {
  const status = err instanceof AppError ? err.status : 500;
  const message = err instanceof AppError ? err.message : 'Internal Server Error';
  res.status(status).json({ error: message });
});

Security

import helmet from 'helmet';
import rateLimit from 'express-rate-limit';

app.use(helmet());

const corsOptions: CorsOptions = {
  origin: process.env.ALLOWED_ORIGINS?.split(',') ?? [],
  credentials: true,
};

const limiter = rateLimit({ windowMs: 60_000, max: 100 });
app.use('/api', limiter);

Never use origin: '*' in production when credentials: true.


Health Check

healthRouter.get('/', (_req, res) => {
  res.json({ status: 'ok', uptime: process.uptime() });
});

Kubernetes pattern: return 503 during shutdown to drain load balancer traffic before process exits.


Graceful Shutdown

const httpServer = app.listen(port);

const shutdown = () => {
  httpServer.close(() => process.exit(0));
  setTimeout(() => process.exit(1), 10_000).unref(); // force-kill after 10s
};

process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);

For Socket.IO: call io.close() inside shutdown before httpServer.close().


InversifyJS Integration

Resolve services from the container inside route factories — never new:

export function createApiRouter(container: Container): Router {
  const router = Router();
  const svc = container.get(MyService);
  router.get('/', asyncHandler(async (req, res) => {
    res.json(await svc.list());
  }));
  return router;
}

Testing with Supertest

import request from 'supertest';
import { buildApp } from '../app.js';

const app = buildApp(testContainer);

it('GET /health returns 200', async () => {
  await request(app).get('/health').expect(200);
});

Bind a real or stub container — avoid mocking the HTTP layer itself.


Common Pitfalls

  • Forgetting next(err) in Express 4 async handlers causes silent hangs.
  • Registering error middleware with three params — it silently becomes a regular middleware.
  • Calling app.listen() inside the app factory breaks supertest (port conflicts).
  • Setting origin: '*' with credentials: true is rejected by browsers.
  • Static middleware before helmet() can serve files without security headers.
  • Not calling io.close() on shutdown leaves Socket.IO connections hanging.

Sources:

Install via CLI
npx skills add https://github.com/recca0120/code-quest --skill express-server
Repository Details
star Stars 9
call_split Forks 2
navigation Branch main
article Path SKILL.md
More from Creator