Get the FREE Ultimate OpenClaw Setup Guide →

express

npx machina-cli add skill Squirrelfishcityhall150/claude-code-kit/express --openclaw
Files (1)
SKILL.md
10.4 KB

Express.js Framework Patterns

Purpose

Essential Express.js patterns for building scalable backend APIs, emphasizing clean routing, middleware composition, and proper request/response handling.

When to Use This Skill

  • Creating or modifying Express routes
  • Building middleware (auth, validation, error handling)
  • Working with Express Request/Response objects
  • Implementing BaseController pattern
  • Error handling in Express

Clean Route Pattern

Routes Only Route

Routes should ONLY:

  • ✅ Define route paths
  • ✅ Register middleware
  • ✅ Delegate to controllers

Routes should NEVER:

  • ❌ Contain business logic
  • ❌ Access database directly
  • ❌ Implement validation logic
  • ❌ Format complex responses
import { Router } from 'express';
import { UserController } from '../controllers/UserController';
import { SSOMiddlewareClient } from '../middleware/SSOMiddleware';

const router = Router();
const controller = new UserController();

// Clean delegation - no business logic
router.get('/:id',
    SSOMiddlewareClient.verifyLoginStatus,
    async (req, res) => controller.getUser(req, res)
);

router.post('/',
    SSOMiddlewareClient.verifyLoginStatus,
    async (req, res) => controller.createUser(req, res)
);

export default router;

BaseController Pattern

Implementation

import * as Sentry from '@sentry/node';
import { Response } from 'express';

export abstract class BaseController {
    protected handleError(
        error: unknown,
        res: Response,
        context: string,
        statusCode = 500
    ): void {
        Sentry.withScope((scope) => {
            scope.setTag('controller', this.constructor.name);
            scope.setTag('operation', context);
            Sentry.captureException(error);
        });

        res.status(statusCode).json({
            success: false,
            error: {
                message: error instanceof Error ? error.message : 'An error occurred',
                code: statusCode,
            },
        });
    }

    protected handleSuccess<T>(
        res: Response,
        data: T,
        message?: string,
        statusCode = 200
    ): void {
        res.status(statusCode).json({
            success: true,
            message,
            data,
        });
    }

    protected async withTransaction<T>(
        name: string,
        operation: string,
        callback: () => Promise<T>
    ): Promise<T> {
        return await Sentry.startSpan({ name, op: operation }, callback);
    }

    protected addBreadcrumb(
        message: string,
        category: string,
        data?: Record<string, any>
    ): void {
        Sentry.addBreadcrumb({ message, category, level: 'info', data });
    }
}

Using BaseController

import { Request, Response } from 'express';
import { BaseController } from './BaseController';
import { UserService } from '../services/userService';
import { createUserSchema } from '../validators/userSchemas';

export class UserController extends BaseController {
    private userService: UserService;

    constructor() {
        super();
        this.userService = new UserService();
    }

    async getUser(req: Request, res: Response): Promise<void> {
        try {
            this.addBreadcrumb('Fetching user', 'user_controller', {
                userId: req.params.id
            });

            const user = await this.userService.findById(req.params.id);

            if (!user) {
                return this.handleError(
                    new Error('User not found'),
                    res,
                    'getUser',
                    404
                );
            }

            this.handleSuccess(res, user);
        } catch (error) {
            this.handleError(error, res, 'getUser');
        }
    }

    async createUser(req: Request, res: Response): Promise<void> {
        try {
            const validated = createUserSchema.parse(req.body);

            const user = await this.withTransaction(
                'user.create',
                'db.query',
                () => this.userService.create(validated)
            );

            this.handleSuccess(res, user, 'User created successfully', 201);
        } catch (error) {
            this.handleError(error, res, 'createUser');
        }
    }
}

Middleware Patterns

Authentication

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { config } from '../config/unifiedConfig';

export class SSOMiddlewareClient {
    static verifyLoginStatus(req: Request, res: Response, next: NextFunction): void {
        const token = req.cookies.refresh_token;

        if (!token) {
            return res.status(401).json({ error: 'Not authenticated' });
        }

        try {
            const decoded = jwt.verify(token, config.tokens.jwt);
            res.locals.claims = decoded;
            res.locals.effectiveUserId = decoded.sub;
            next();
        } catch (error) {
            res.status(401).json({ error: 'Invalid token' });
        }
    }
}

Audit with AsyncLocalStorage

import { Request, Response, NextFunction } from 'express';
import { AsyncLocalStorage } from 'async_hooks';
import { v4 as uuidv4 } from 'uuid';

export interface AuditContext {
    userId: string;
    userName?: string;
    requestId: string;
    timestamp: Date;
}

export const auditContextStorage = new AsyncLocalStorage<AuditContext>();

export function auditMiddleware(req: Request, res: Response, next: NextFunction): void {
    const context: AuditContext = {
        userId: res.locals.effectiveUserId || 'anonymous',
        userName: res.locals.claims?.preferred_username,
        timestamp: new Date(),
        requestId: req.id || uuidv4(),
    };

    auditContextStorage.run(context, () => next());
}

export function getAuditContext(): AuditContext | null {
    return auditContextStorage.getStore() || null;
}

Error Boundary

import { Request, Response, NextFunction } from 'express';
import * as Sentry from '@sentry/node';

export function errorBoundary(
    error: Error,
    req: Request,
    res: Response,
    next: NextFunction
): void {
    const statusCode = error.statusCode || 500;

    Sentry.captureException(error);

    res.status(statusCode).json({
        success: false,
        error: {
            message: error.message,
            code: error.name,
        },
    });
}

// Async wrapper
export function asyncErrorWrapper(
    handler: (req: Request, res: Response, next: NextFunction) => Promise<any>
) {
    return async (req: Request, res: Response, next: NextFunction) => {
        try {
            await handler(req, res, next);
        } catch (error) {
            next(error);
        }
    };
}

Middleware Ordering

Critical Order

import express from 'express';
import * as Sentry from '@sentry/node';

const app = express();

// 1. Sentry request handler (FIRST)
app.use(Sentry.Handlers.requestHandler());

// 2. Body/cookie parsing
app.use(express.json());
app.use(cookieParser());

// 3. Routes
app.use('/api/users', userRoutes);

// 4. Error handler (AFTER routes)
app.use(errorBoundary);

// 5. Sentry error handler (LAST)
app.use(Sentry.Handlers.errorHandler());

Rules:

  • Sentry request handler FIRST
  • Body/cookie parsers before routes
  • Error handlers AFTER all routes
  • Sentry error handler LAST

Request/Response Handling

Typed Requests

interface CreateUserRequest {
    email: string;
    name: string;
    password: string;
}

async function createUser(
    req: Request<{}, {}, CreateUserRequest>,
    res: Response
): Promise<void> {
    const { email, name, password } = req.body; // Typed
}

Response Patterns

// Success (200)
res.json({ success: true, data: user });

// Created (201)
res.status(201).json({ success: true, data: user });

// Error (400/500)
res.status(400).json({ success: false, error: { message: 'Invalid input' } });

HTTP Status Codes

CodeUse Case
200Success (GET, PUT)
201Created (POST)
204No Content (DELETE)
400Bad Request
401Unauthorized
403Forbidden
404Not Found
500Server Error

Common Mistakes

1. Business Logic in Routes

// ❌ Never do this
router.post('/submit', async (req, res) => {
    // 100+ lines of logic
    const user = await db.user.create(req.body);
    const workflow = await processWorkflow(user);
    res.json(workflow);
});

// ✅ Do this
router.post('/submit', (req, res) => controller.submit(req, res));

2. Wrong Middleware Order

// ❌ Error handler before routes
app.use(errorBoundary);
app.use('/api', routes); // Won't catch errors

// ✅ Error handler after routes
app.use('/api', routes);
app.use(errorBoundary);

3. No Error Handling

// ❌ Unhandled errors crash server
router.get('/user/:id', async (req, res) => {
    const user = await userService.get(req.params.id); // May throw
    res.json(user);
});

// ✅ Proper error handling
async getUser(req: Request, res: Response): Promise<void> {
    try {
        const user = await this.userService.get(req.params.id);
        this.handleSuccess(res, user);
    } catch (error) {
        this.handleError(error, res, 'getUser');
    }
}

Common Imports

// Express core
import express, { Request, Response, NextFunction, Router } from 'express';

// Middleware
import cookieParser from 'cookie-parser';
import cors from 'cors';

// Sentry
import * as Sentry from '@sentry/node';

// Utilities
import { AsyncLocalStorage } from 'async_hooks';

Best Practices

  1. Keep Routes Clean - Routes only route, delegate to controllers
  2. Use BaseController - Consistent error handling and response formatting
  3. Proper Middleware Order - Sentry → Parsers → Routes → Error handlers
  4. Type Everything - Use TypeScript for Request/Response types
  5. Handle All Errors - Use try-catch in controllers, error boundaries globally

Related Skills:

  • nodejs - Core Node.js patterns and async handling
  • backend-dev-guidelines - Complete backend architecture guide
  • prisma - Database patterns with Prisma ORM

Source

git clone https://github.com/Squirrelfishcityhall150/claude-code-kit/blob/main/cli/kits/express/skills/express/SKILL.mdView on GitHub

Overview

Essential Express.js patterns for building scalable backend APIs, focusing on clean routing, middleware composition, and proper request/response handling. This skill helps you structure Express apps to delegate business logic to controllers and implement robust error handling.

How This Skill Works

Routes are kept as delegators that only define paths and register middleware, then hand off to controllers. The BaseController pattern provides standardized responses, error handling, and observability (via Sentry) across controllers. Use code patterns like the Clean Route Pattern and BaseController to enforce separation of concerns.

When to Use It

  • Creating or modifying Express routes
  • Building middleware (auth, validation, error handling)
  • Working with Express Request/Response objects
  • Implementing BaseController pattern
  • Error handling in Express

Quick Start

  1. Step 1: Create an Express Router and wire routes to controller methods (no business logic).
  2. Step 2: Add middleware for auth/validation and use controllers to perform processing.
  3. Step 3: Extend BaseController in your controllers and use handleError/handleSuccess.

Best Practices

  • Keep routes slim and delegate business logic to controllers
  • Never place business logic or DB access in route handlers
  • Use middleware for cross-cutting concerns (auth, validation, errors)
  • Adopt the BaseController pattern for consistent responses and errors
  • Centralize error reporting and observability (e.g., Sentry) with proper logging

Example Use Cases

  • Router wiring: define route paths, register middleware, and delegate to controllers.
  • UserController extends BaseController to reuse handleError and handleSuccess.
  • SSOMiddlewareClient.verifyLoginStatus is used in routes to secure endpoints.
  • Controller methods call this.handleSuccess(res, data) or this.handleError(...) for errors.
  • Using withTransaction and addBreadcrumb from BaseController to instrument requests and trace performance.

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers