Agent skill
express
Express.js framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Express.js projects, or when the user mentions Express. Provides middleware patterns, routing, error handling, and REST API guidelines.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/express-ar4mirez-samuel
Metadata
Additional technical details for this skill
- author
- samuel
- version
- 1.0
- category
- framework
- language
- typescript
- extensions
- .js,.ts
SKILL.md
Express.js Guide
Applies to: Express.js 4.x/5.x, TypeScript/JavaScript, REST APIs, Web Servers, Microservices
Core Principles
- Middleware-Centric: Everything is middleware -- parsing, auth, logging, errors
- Layered Architecture: Controllers -> Services -> Repositories (separation of concerns)
- Type Safety: Use TypeScript for all new Express projects
- Security First: helmet, cors, rate limiting, input validation on every route
- Graceful Lifecycle: Handle startup, shutdown, and uncaught errors properly
Guardrails
Project Setup
- Use TypeScript with strict mode enabled
- Install core security packages:
helmet,cors,express-rate-limit - Use
dotenvfor environment configuration (never hardcode secrets) - Use
zodorjoifor request validation at every API boundary - Use
morganorpinofor structured logging - Set
express.json({ limit: '10mb' })to prevent oversized payloads
Code Style
- Controllers: thin, delegate to services, catch errors via
next(error) - Services: business logic only, no
req/resreferences - Repositories: data access only, return domain types
- Middleware: single-responsibility, composable
- Routes: declarative, grouped by resource
- No business logic in route files
Error Handling
- Use a centralized error-handling middleware (4-argument signature)
- Create a custom
AppErrorclass withstatusCodeand optionalerrorsarray - Always call
next(error)in async handlers -- never swallow errors - Return generic messages in production, detailed messages in development
- Handle
404with a dedicated not-found middleware after all routes - Log all errors with request context (path, method, stack trace)
Security
- Enable
helmet()for security headers - Configure CORS with explicit allowed origins (not
*in production) - Implement rate limiting on all routes, stricter on auth endpoints
- Validate and sanitize all user inputs before processing
- Use parameterized queries (Prisma, Knex, or prepared statements)
- Set secure cookie options:
httpOnly,secure,sameSite - Never expose stack traces or internal details in production responses
Performance
- Use
compressionmiddleware for response compression - Implement pagination for list endpoints (never return unbounded results)
- Use connection pooling for database connections
- Set appropriate timeouts on the HTTP server (
readTimeout,writeTimeout) - Cache expensive computations where appropriate
Project Structure
my-api/
├── src/
│ ├── app.ts # Express app setup (middleware, routes, error handling)
│ ├── server.ts # Entry point (listen, graceful shutdown)
│ ├── config/ # Environment and app configuration
│ │ └── index.ts
│ ├── controllers/ # Route handlers (thin, delegate to services)
│ │ └── user.controller.ts
│ ├── middlewares/ # Custom middleware
│ │ ├── auth.middleware.ts
│ │ ├── error.middleware.ts
│ │ ├── validate.middleware.ts
│ │ └── rateLimit.middleware.ts
│ ├── routes/ # Route definitions (grouped by resource)
│ │ ├── index.ts
│ │ └── user.routes.ts
│ ├── services/ # Business logic (no req/res)
│ │ └── user.service.ts
│ ├── repositories/ # Data access layer
│ │ └── user.repository.ts
│ ├── models/ # Data models / Prisma schema
│ │ └── user.model.ts
│ ├── schemas/ # Zod validation schemas
│ │ └── user.schema.ts
│ ├── types/ # TypeScript type definitions
│ │ └── index.ts
│ └── utils/ # Shared utilities
│ ├── errors.ts # AppError class
│ └── logger.ts # Logger setup
├── tests/
│ └── user.test.ts
├── .env.example
├── package.json
└── tsconfig.json
Layer Responsibilities
| Layer | Knows About | Never References |
|---|---|---|
| Routes | Controllers, middleware | Services, repositories |
| Controllers | Services, types | Repositories, database |
| Services | Repositories, types | req/res, Express |
| Repositories | Database client, types | Services, controllers |
Application Setup
App Configuration (src/app.ts)
import express, { Application } from 'express';
import cors from 'cors';
import helmet from 'helmet';
import morgan from 'morgan';
import { errorHandler } from './middlewares/error.middleware';
import { notFoundHandler } from './middlewares/notFound.middleware';
import routes from './routes';
const app: Application = express();
// Security
app.use(helmet());
app.use(cors({ origin: process.env.CORS_ORIGIN || '*', credentials: true }));
// Parsing
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Logging (skip in test)
if (process.env.NODE_ENV !== 'test') {
app.use(morgan('combined'));
}
// Health check
app.get('/health', (_req, res) => {
res.json({ status: 'ok', timestamp: new Date().toISOString() });
});
// API routes
app.use('/api/v1', routes);
// Error handling (order matters: 404 first, then error handler)
app.use(notFoundHandler);
app.use(errorHandler);
export default app;
Server Entry Point (src/server.ts)
import 'dotenv/config';
import app from './app';
import { logger } from './utils/logger';
const PORT = process.env.PORT || 3000;
const server = app.listen(PORT, () => {
logger.info(`Server running on port ${PORT}`);
});
// Graceful shutdown
const shutdown = () => {
logger.info('Shutting down gracefully...');
server.close(() => process.exit(0));
setTimeout(() => process.exit(1), 10000); // Force after 10s
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
process.on('unhandledRejection', (reason: Error) => {
logger.error('Unhandled Rejection:', reason);
throw reason;
});
process.on('uncaughtException', (error: Error) => {
logger.error('Uncaught Exception:', error);
process.exit(1);
});
Routing
Route Organization
// src/routes/index.ts
import { Router } from 'express';
import userRoutes from './user.routes';
import authRoutes from './auth.routes';
const router = Router();
router.use('/auth', authRoutes);
router.use('/users', userRoutes);
export default router;
Resource Routes
// src/routes/user.routes.ts
import { Router } from 'express';
import { UserController } from '../controllers/user.controller';
import { authMiddleware } from '../middlewares/auth.middleware';
import { validate } from '../middlewares/validate.middleware';
import { createUserSchema, updateUserSchema } from '../schemas/user.schema';
const router = Router();
const controller = new UserController();
router.get('/', controller.getAll);
router.get('/:id', controller.getById);
router.post('/', validate(createUserSchema), controller.create);
router.put('/:id', authMiddleware, validate(updateUserSchema), controller.update);
router.delete('/:id', authMiddleware, controller.delete);
export default router;
Route Conventions
- Group routes by resource under
/api/v1/ - Use plural nouns for resource names (
/users,/products) - HTTP verbs map to CRUD: GET (read), POST (create), PUT/PATCH (update), DELETE (remove)
- Apply auth middleware selectively (not on public routes)
- Apply validation middleware before the controller handler
Middleware
Middleware Order (in app.ts)
helmet()-- security headerscors()-- cross-origin requestsexpress.json()-- body parsingmorgan()-- request logging- Route-level middleware (auth, validation, rate limiting)
- Routes
notFoundHandler-- catch unmatched routeserrorHandler-- centralized error handling
Custom AppError
// src/utils/errors.ts
export class AppError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public errors?: any[]
) {
super(message);
this.name = 'AppError';
Error.captureStackTrace(this, this.constructor);
}
}
Error Handler
// src/middlewares/error.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../utils/errors';
import { logger } from '../utils/logger';
export const errorHandler = (
error: Error, req: Request, res: Response, _next: NextFunction
) => {
logger.error('Error:', { message: error.message, path: req.path, method: req.method });
if (error instanceof AppError) {
return res.status(error.statusCode).json({
status: 'error',
message: error.message,
...(error.errors && { errors: error.errors }),
});
}
res.status(500).json({
status: 'error',
message: process.env.NODE_ENV === 'production'
? 'Internal server error'
: error.message,
});
};
Validation Middleware (Zod)
// src/middlewares/validate.middleware.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema, ZodError } from 'zod';
import { AppError } from '../utils/errors';
export const validate = (schema: ZodSchema) => {
return (req: Request, _res: Response, next: NextFunction) => {
try {
schema.parse({ body: req.body, query: req.query, params: req.params });
next();
} catch (error) {
if (error instanceof ZodError) {
const errors = error.errors.map((e) => ({
field: e.path.join('.'),
message: e.message,
}));
next(new AppError('Validation failed', 400, errors));
} else {
next(error);
}
}
};
};
Testing
Testing Stack
- Jest for test runner and assertions
- Supertest for HTTP integration tests
- Use
app(notserver) for supertest -- avoids port conflicts
Integration Test Pattern
import request from 'supertest';
import app from '../src/app';
describe('GET /api/v1/users', () => {
it('should return 200 with user list', async () => {
const response = await request(app)
.get('/api/v1/users')
.expect('Content-Type', /json/)
.expect(200);
expect(response.body.data).toBeDefined();
expect(response.body.meta).toBeDefined();
});
it('should return 400 for invalid query params', async () => {
await request(app)
.get('/api/v1/users?page=-1')
.expect(400);
});
});
Test Organization
- One test file per resource/feature
- Group by HTTP method and route using
describeblocks - Test both success and error paths
- Test validation, auth, and edge cases
- Use
beforeEach/afterEachfor database cleanup
Tooling
Essential Commands
# Development
npm run dev # Start with hot-reload (ts-node-dev)
# Build
npm run build # Compile TypeScript
npm start # Run compiled JS
# Testing
npm test # Run all tests
npm run test:watch # Watch mode
npm run test:cov # With coverage
# Quality
npm run lint # ESLint
npm run lint:fix # Auto-fix lint issues
Configuration (tsconfig.json)
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Package Scripts
{
"scripts": {
"dev": "ts-node-dev --respawn --transpile-only src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"test": "jest",
"lint": "eslint src/**/*.ts"
}
}
Dependencies
Core
| Package | Purpose |
|---|---|
express |
Web framework |
cors |
Cross-origin resource sharing |
helmet |
Security headers |
morgan |
HTTP request logging |
dotenv |
Environment variables |
zod |
Input validation |
Auth & Security
| Package | Purpose |
|---|---|
jsonwebtoken |
JWT authentication |
bcryptjs |
Password hashing |
express-rate-limit |
Rate limiting |
Database
| Package | Purpose |
|---|---|
@prisma/client |
ORM / database client |
Dev
| Package | Purpose |
|---|---|
typescript |
Type system |
ts-node-dev |
Dev server with hot reload |
jest |
Test runner |
supertest |
HTTP testing |
@types/express |
Express type definitions |
Advanced Topics
For detailed code examples and advanced patterns, see:
- references/patterns.md -- Controller patterns, service layer, authentication, database integration, rate limiting, testing examples
External References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?