AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Nodejs Express Skill

skill-j4flmao-agent-skills-express · by j4flmao

>

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

Install

$ agentstack add skill-j4flmao-agent-skills-express

✓ 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 No
  • 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.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-j4flmao-agent-skills-express)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Nodejs Express Skill? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Node.js Express

Purpose

Structure Express.js applications with clean middleware pipeline, error handling, route separation, and validation.

Agent Protocol

Trigger

User request includes: express, express.js, middleware, router, next.js error handling, express app, express setup, express routing, express validation.

Input Context

  • App scope (REST API, GraphQL BFF, SSR)
  • TypeScript or JavaScript
  • Database (Prisma, Mongoose, raw)
  • Auth strategy (JWT, session, OAuth)

Output Artifact

Project structure, middleware order, route layout, error handler, validation setup.

Response Format

Produce artifact directly. No preamble, no postamble, no explanations. No filler, no hedging, no transitions. Strip articles a/an/the where unambiguous. Compress output — why use many token when few do trick.

Completion Criteria

  • Middleware pipeline ordered security -> parsing -> logging -> rate-limit -> routes -> 404 -> error
  • Global error handler catches sync + async errors
  • All input validated via schema library
  • Routes organized by domain module

Max Response Length

4096 tokens

Architecture Decision Trees

Express vs Fastify vs Hono (within Node.js)

| Criterion | Express | Fastify | Hono | |-----------|---------|---------|------| | Performance | ~30k req/s | ~50k req/s | ~60k req/s | | Plugin ecosystem | Largest | Growing | Small | | TypeScript | Manual annotations | Schema-first (TypeBox/Zod) | Full (TypeBox) | | Serialization | JSON.stringify | Fast JSON serialization | Built-in | | Middleware model | Callback chain | Plugin registration | Express-like | | Validation | Manual middleware | Schema-compiled serializer | Middleware-based |

Decision: Largest ecosystem / wide support → Express. Performance + schema-first → Fastify. Edge/Cloudflare Workers → Hono.

Middleware Order Decision

| Layer | Middleware | Position | |-------|-----------|----------| | Security | Helmet, CORS, CSP | 1st (before any body) | | Parsing | JSON, URL-encoded | 2nd | | Observability | Logger, request ID | 3rd | | Protection | Rate limiter | 4th | | Auth | JWT/Session check | 5th | | Routes | Domain routers | 6th | | Fallback | 404 handler | 7th | | Error | Global error handler | Last |

Workflow

Step 1: Project Bootstrap

express-app/
  src/
    app.ts                    # Express app factory
    server.ts                 # HTTP server entry
    config/
      env.ts                  # Typed env vars
      database.ts
      redis.ts
    modules/
      users/
        user.controller.ts
        user.service.ts
        user.repository.ts
        user.validation.ts
        user.routes.ts
        user.test.ts
      orders/
        ...
    common/
      middleware/
        auth.ts
        error-handler.ts
        request-logger.ts
        rate-limiter.ts
        validate.ts
      errors/
        app-error.ts
        not-found.ts
      types/
        express.d.ts
        index.ts
    shared/
      logger.ts
      pagination.ts
  package.json
  tsconfig.json
  .env.example

Step 2: App Factory Pattern

// app.ts
import express from 'express';
import cors from 'cors';
import helmet from 'helmet';
import compression from 'compression';
import { requestLogger } from './common/middleware/request-logger';
import { rateLimiter } from './common/middleware/rate-limiter';
import { notFoundHandler } from './common/middleware/not-found';
import { errorHandler } from './common/middleware/error-handler';
import { routes } from './modules/routes';

export function createApp() {
  const app = express();

  // Security
  app.use(cors({ origin: env.CORS_ORIGIN }));
  app.use(helmet());
  app.use(compression());

  // Parsing
  app.use(express.json({ limit: '1mb' }));
  app.use(express.urlencoded({ extended: true }));

  // Observability
  app.use(requestLogger);

  // Rate limiting
  app.use(rateLimiter);

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

  // Error handling
  app.use(notFoundHandler);
  app.use(errorHandler);

  return app;
}

Step 3: Graceful Server Startup

// server.ts
import { createApp } from './app';
import { logger } from './shared/logger';

async function main() {
  const app = createApp();
  const server = app.listen(env.PORT, () => {
    logger.info(`Server listening on port ${env.PORT}`);
  });

  const shutdown = (signal: string) => {
    logger.info(`${signal} received. Shutting down...`);
    server.close(() => {
      logger.info('Server closed');
      process.exit(0);
    });
  };

  process.on('SIGTERM', () => shutdown('SIGTERM'));
  process.on('SIGINT', () => shutdown('SIGINT'));
}

main().catch((err) => {
  console.error('Fatal startup error:', err);
  process.exit(1);
});

Step 4: Environment Configuration

// config/env.ts
import { z } from 'zod';
import dotenv from 'dotenv';

dotenv.config({ path: `.env.${process.env.NODE_ENV || 'development'}` });

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url().optional(),
  CORS_ORIGIN: z.string().default('*'),
  JWT_SECRET: z.string().min(32),
  LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
});

export const env = envSchema.parse(process.env);

Step 5: Module Route Setup

// modules/users/user.controller.ts
import { Request, Response, NextFunction } from 'express';
import { userService } from './user.service';
import { asyncHandler } from '../../common/middleware/async-handler';

export const userController = {
  list: asyncHandler(async (req: Request, res: Response) => {
    const users = await userService.findAll(req.query);
    res.json({ success: true, data: users });
  }),

  getById: asyncHandler(async (req: Request, res: Response) => {
    const user = await userService.findById(req.params.id);
    res.json({ success: true, data: user });
  }),

  create: asyncHandler(async (req: Request, res: Response) => {
    const user = await userService.create(req.body);
    res.status(201).json({ success: true, data: user });
  }),

  update: asyncHandler(async (req: Request, res: Response) => {
    const user = await userService.update(req.params.id, req.body);
    res.json({ success: true, data: user });
  }),

  remove: asyncHandler(async (req: Request, res: Response) => {
    await userService.remove(req.params.id);
    res.status(204).send();
  }),
};

// modules/users/user.routes.ts
import { Router } from 'express';
import { userController } from './user.controller';
import { validate } from '../../common/middleware/validate';
import { createUserSchema, updateUserSchema } from './user.validation';
import { authenticate } from '../../common/middleware/auth';

const router = Router();

router.use(authenticate);

router.get('/', userController.list);
router.get('/:id', userController.getById);
router.post('/', validate(createUserSchema), userController.create);
router.put('/:id', validate(updateUserSchema), userController.update);
router.delete('/:id', userController.remove);

export default router;

Step 6: Validation Middleware

// common/middleware/validate.ts
import { Request, Response, NextFunction } from 'express';
import { ZodSchema, ZodError } from 'zod';
import { AppError } from '../errors/app-error';

export function validate(schema: ZodSchema, source: 'body' | 'query' | 'params' = 'body') {
  return (req: Request, _res: Response, next: NextFunction) => {
    try {
      req[source] = schema.parse(req[source]);
      next();
    } catch (err) {
      if (err instanceof ZodError) {
        next(new AppError(400, 'VALIDATION_ERROR', 'Invalid request data', err.errors));
      } else {
        next(err);
      }
    }
  };
}

Step 7: Error Handling

// common/middleware/error-handler.ts
import { Request, Response, NextFunction } from 'express';
import { AppError } from '../errors/app-error';
import { logger } from '../../shared/logger';

export function errorHandler(err: Error, _req: Request, res: Response, _next: NextFunction) {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      success: false,
      error: {
        code: err.code,
        message: err.message,
        details: err.details,
      },
    });
  }

  logger.error('Unhandled error', { error: err.message, stack: err.stack });

  return res.status(500).json({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: 'An unexpected error occurred',
    },
  });
}

// common/middleware/async-handler.ts
import { Request, Response, NextFunction } from 'express';

export function asyncHandler(fn: (req: Request, res: Response, next: NextFunction) => Promise) {
  return (req: Request, res: Response, next: NextFunction) => {
    Promise.resolve(fn(req, res, next)).catch(next);
  };
}

// common/errors/app-error.ts
export class AppError extends Error {
  constructor(
    public readonly statusCode: number,
    public readonly code: string,
    message: string,
    public readonly details?: unknown
  ) {
    super(message);
    this.name = 'AppError';
  }
}

Step 8: Auth Middleware

// common/middleware/auth.ts
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import { env } from '../../config/env';
import { AppError } from '../errors/app-error';

export interface AuthPayload {
  userId: string;
  role: string;
}

declare global {
  namespace Express {
    interface Request {
      user?: AuthPayload;
    }
  }
}

export function authenticate(req: Request, _res: Response, next: NextFunction) {
  const header = req.headers.authorization;
  if (!header?.startsWith('Bearer ')) {
    return next(new AppError(401, 'UNAUTHORIZED', 'Missing or invalid token'));
  }

  try {
    const token = header.slice(7);
    req.user = jwt.verify(token, env.JWT_SECRET) as AuthPayload;
    next();
  } catch {
    next(new AppError(401, 'UNAUTHORIZED', 'Token expired or invalid'));
  }
}

export function authorize(...roles: string[]) {
  return (req: Request, _res: Response, next: NextFunction) => {
    if (!req.user || !roles.includes(req.user.role)) {
      return next(new AppError(403, 'FORBIDDEN', 'Insufficient permissions'));
    }
    next();
  };
}

Implementation Patterns

Pattern: Dependency Injection Container (awilix)

// shared/container.ts
import { createContainer, asClass, asValue, Lifetime } from 'awilix';
import { PrismaClient } from '@prisma/client';

const container = createContainer();

container.register({
  prisma: asValue(new PrismaClient()),
  userService: asClass(UserService).scoped(Lifetime.SCOPED),
  userController: asClass(UserController).scoped(Lifetime.SCOPED),
});

export { container };

// app.ts — DI middleware
app.use((req, res, next) => {
  req.container = container.createScope();
  next();
});

Pattern: Health Check Endpoint

// modules/health/health.controller.ts
export const healthController = {
  check: asyncHandler(async (req: Request, res: Response) => {
    const checks = {
      database: await checkDatabase(),
      redis: await checkRedis(),
      uptime: process.uptime(),
    };
    const healthy = Object.values(checks).every(c => c.status === 'ok');
    res.status(healthy ? 200 : 503).json({
      status: healthy ? 'healthy' : 'degraded',
      checks,
    });
  }),
};

Production Considerations

Performance

  • Enable trust proxy behind nginx/ELB: app.set('trust proxy', 1)
  • Use response compression: compression() middleware
  • Limit body size: express.json({ limit: '1mb' })
  • Cluster mode: pm2 start app.js -i max
  • Memory: monitor with node --heapsnapshot-signal=SIGUSR2

Security Headers

app.use(helmet({
  contentSecurityPolicy: { directives: { defaultSrc: ["'self'"] } },
  hsts: { maxAge: 31536000, includeSubDomains: true },
  referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
}));

Anti-Patterns

| Anti-Pattern | Why | Fix | |-------------|-----|-----| | app.listen in app module | Can't test app without starting server | App factory pattern | | Naked try/catch in controllers | Duplicated error handling | asyncHandler wrapper | | Direct req.body without validation | Security vulnerability | Zod schema in middleware | | Multiple res.json calls in one handler | ERR_HTTP_HEADERS_SENT | Single code path | | Sync crypto.randomBytes in request | Blocks event loop | Use crypto.randomBytes callback or async |

Security Considerations

  • Helmet sets security headers — always include first in middleware chain
  • CORS with explicit origin list — never * with credentials
  • Rate limiting per IP per route group
  • JWT: validate algorithm, set short expiration (15m access + 7d refresh)
  • SQL injection: use parameterized queries via Prisma/Knex — never raw string concat
  • CSRF: csurf middleware for cookie/session auth
  • Input validation: Zod with .strip() (default) — remove unknown properties

Testing Strategies

import request from 'supertest';
import { createApp } from '../src/app';

const app = createApp();

describe('POST /api/v1/users', () => {
  it('should create user with valid data', async () => {
    const res = await request(app)
      .post('/api/v1/users')
      .send({ name: 'John', email: 'john@test.com' })
      .expect(201);
    expect(res.body.success).toBe(true);
    expect(res.body.data).toHaveProperty('id');
  });

  it('should return 400 for invalid email', async () => {
    const res = await request(app)
      .post('/api/v1/users')
      .send({ name: 'John', email: 'invalid' })
      .expect(400);
    expect(res.body.error.code).toBe('VALIDATION_ERROR');
  });
});

Use testcontainers for DB integration. Mock HTTP with nock. Use jest with --detectOpenHandles for unclosed connections.

Rules

  • Middleware order: security -> parsing -> logging -> rate-limit -> routes -> 404 -> error. Never deviate.
  • All async handlers wrapped with asyncHandler. No try/catch in controllers.
  • Input validation via Zod schema in middleware. Never trust raw req.body.
  • App factory pattern (createApp function) for testability. No top-level app.listen.
  • Environment config validated at startup with Zod. Fail fast on missing vars.
  • Each domain module gets own router, controller, service, validation.
  • Services stateless. Dependencies injected via constructor or module-level composition root.
  • @types/express augmented for custom properties (req.user).
  • Graceful shutdown on SIGTERM/SIGINT. Close server + DB connections.

References

  • references/app-structure.md — App Structure
  • references/express-error-handling.md — Express Error Handling Reference
  • references/express-security.md — Express Security Reference
  • references/middleware-patterns.md — Middleware Patterns
  • references/performance-optimization.md — Express Performance Optimization
  • references/testing-strategies.md — Express Testing Strategies

Handoff

Hand off to backend/nodejs/prisma/SKILL.md for database integration or backend/nodejs/patterns/SKILL.md for advanced Express patterns.

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.