AgentStack
SKILL verified MIT Self-run

Express Api

skill-dallay-agents-skills-express-api · by dallay

>-

No reviews yet
0 installs
10 views
0.0% view→install

Install

$ agentstack add skill-dallay-agents-skills-express-api

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Express Api? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

When to Use

  • Building or refactoring a REST API with Express.js.
  • Setting up middleware chains for authentication, logging, or validation.
  • Structuring an Express project for maintainability and testability.
  • Adding security headers, rate limiting, or CORS configuration.
  • Implementing centralized error handling across routes.

Critical Patterns

  • Layered Architecture: Separate routes → controllers → services → repositories. Routes define

endpoints, controllers handle HTTP concerns, services contain business logic, repositories handle data access.

  • Centralized Error Handling: NEVER scatter try/catch in every route. Use an async wrapper and

a single error-handling middleware at the end of the middleware chain.

  • Validate at the Edge: Validate ALL incoming data (body, params, query) at the route level

using Zod or Joi BEFORE it reaches the controller.

  • Security by Default: Always use helmet, configure cors explicitly (never * in

production), and apply rate limiting to public endpoints.

  • Environment Config: Use a validated config module. NEVER access process.env directly

throughout the codebase.

  • Consistent Response Shape: Every API response should follow the same envelope:

{ data, error, meta }.

Project Structure

src/
├── config/
│   └── env.ts              # Validated environment config
├── middleware/
│   ├── errorHandler.ts      # Centralized error handler
│   ├── validate.ts          # Request validation middleware
│   ├── auth.ts              # Authentication middleware
│   └── rateLimiter.ts       # Rate limiting config
├── routes/
│   ├── index.ts             # Route aggregator
│   └── users.routes.ts      # /users route definitions
├── controllers/
│   └── users.controller.ts  # HTTP concern handling
├── services/
│   └── users.service.ts     # Business logic
├── repositories/
│   └── users.repository.ts  # Data access
├── errors/
│   └── AppError.ts          # Custom error classes
├── utils/
│   └── asyncHandler.ts      # Async error wrapper
└── app.ts                   # Express app setup

Code Examples

App Setup with Security Middleware

import express from "express";
import helmet from "helmet";
import cors from "cors";
import { rateLimit } from "express-rate-limit";
import { errorHandler } from "./middleware/errorHandler";
import { routes } from "./routes";

const app = express();

// Security middleware — order matters
app.use(helmet());
app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(",") ?? [],
  credentials: true,
}));
app.use(rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
}));

// Body parsing
app.use(express.json({ limit: "10kb" }));
app.use(express.urlencoded({ extended: true }));

// Routes
app.use("/api/v1", routes);

// Health check — outside versioned routes
app.get("/health", (_req, res) => {
  res.json({ status: "ok" });
});

// Centralized error handler — MUST be last
app.use(errorHandler);

export { app };

Async Error Wrapper

import { Request, Response, NextFunction, RequestHandler } from "express";

// Wraps async route handlers so thrown errors reach the error middleware
export const asyncHandler = (
  fn: (req: Request, res: Response, next: NextFunction) => Promise
): RequestHandler => {
  return (req, res, next) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
};

Custom Error Classes

export class AppError extends Error {
  constructor(
    public readonly statusCode: number,
    public readonly message: string,
    public readonly isOperational = true,
  ) {
    super(message);
    Object.setPrototypeOf(this, new.target.prototype);
    Error.captureStackTrace(this);
  }
}

export class NotFoundError extends AppError {
  constructor(resource: string) {
    super(404, `${resource} not found`);
  }
}

export class ValidationError extends AppError {
  constructor(message: string) {
    super(400, message);
  }
}

Centralized Error Handler

import { Request, Response, NextFunction } from "express";
import { AppError } from "../errors/AppError";
import { ZodError } from "zod";

export function errorHandler(
  err: Error,
  _req: Request,
  res: Response,
  _next: NextFunction,
): void {
  // Zod validation errors
  if (err instanceof ZodError) {
    res.status(400).json({
      error: "Validation failed",
      details: err.errors.map((e) => ({
        path: e.path.join("."),
        message: e.message,
      })),
    });
    return;
  }

  // Known operational errors
  if (err instanceof AppError) {
    res.status(err.statusCode).json({ error: err.message });
    return;
  }

  // Unknown errors — don't leak internals
  console.error("Unhandled error:", err);
  res.status(500).json({ error: "Internal server error" });
}

Request Validation with Zod

import { Request, Response, NextFunction } from "express";
import { AnyZodObject, ZodError } from "zod";

export const validate = (schema: AnyZodObject) => {
  return (req: Request, _res: Response, next: NextFunction) => {
    try {
      schema.parse({
        body: req.body,
        query: req.query,
        params: req.params,
      });
      next();
    } catch (err) {
      next(err); // Caught by errorHandler
    }
  };
};

// Usage — define schemas per route
import { z } from "zod";

export const createUserSchema = z.object({
  body: z.object({
    email: z.string().email(),
    name: z.string().min(2).max(100),
    role: z.enum(["user", "admin"]).default("user"),
  }),
});

Routes → Controller → Service Pattern

// routes/users.routes.ts
import { Router } from "express";
import { asyncHandler } from "../utils/asyncHandler";
import { validate } from "../middleware/validate";
import { createUserSchema } from "../schemas/user.schema";
import * as controller from "../controllers/users.controller";

const router = Router();

router.get("/", asyncHandler(controller.list));
router.get("/:id", asyncHandler(controller.getById));
router.post("/", validate(createUserSchema), asyncHandler(controller.create));

export { router as usersRouter };
// controllers/users.controller.ts
import { Request, Response } from "express";
import * as userService from "../services/users.service";

export async function list(_req: Request, res: Response): Promise {
  const users = await userService.findAll();
  res.json({ data: users });
}

export async function getById(req: Request, res: Response): Promise {
  const user = await userService.findById(req.params.id);
  res.json({ data: user });
}

export async function create(req: Request, res: Response): Promise {
  const user = await userService.create(req.body);
  res.status(201).json({ data: user });
}

Environment Config with Validation

import { z } from "zod";

const envSchema = z.object({
  NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  ALLOWED_ORIGINS: z.string().default("http://localhost:3000"),
});

// Validate once at startup — fail fast
export const env = envSchema.parse(process.env);

Best Practices

DO

  • Use express.Router() to modularize routes by domain.
  • Return appropriate HTTP status codes: 201 for created, 204 for no-content, 404 for not

found.

  • Use API versioning in the URL path (/api/v1/...).
  • Set trust proxy if behind a reverse proxy (for rate limiting and IP detection).
  • Add request ID middleware for tracing across logs.
  • Gracefully shut down: listen for SIGTERM/SIGINT, stop accepting new connections, drain

existing ones.

DON'T

  • DON'T use app.use(cors()) with no options — it allows all origins.
  • DON'T put business logic in route handlers or controllers — keep it in services.
  • DON'T return stack traces or internal error details in production responses.
  • DON'T use express.static for user-uploaded files without path sanitization.
  • DON'T use synchronous file operations (fs.readFileSync) in request handlers.
  • DON'T call res.json() or res.send() more than once per request — it causes "headers already

sent" errors.

  • DON'T forget to call next() in non-terminal middleware — the request will hang.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.