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

Nodejs Architecture

skill-j4flmao-agent-skills-architecture · by j4flmao

>

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

Install

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

✓ 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 Used
  • 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-architecture)

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 Architecture? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Node.js Architecture

Purpose

Define and enforce Node.js backend architecture, framework selection, and middleware pipeline conventions.

Agent Protocol

Trigger

User request includes: node.js, nodejs, express, fastify, hono, node backend, node project structure, middleware, node.js routing.

Input Context

  • Framework (Express, Fastify, Hono)
  • Runtime (Node.js v18+, Bun, Deno)
  • Language (JavaScript, TypeScript)
  • Project type (REST API, GraphQL, BFF)

Output Artifact

A markdown document containing:

  • Project structure
  • Middleware pipeline ordering
  • Routing conventions
  • Error handling strategy
  • Validation setup (Zod, Joi)
  • Dependency injection pattern
  • Testing setup

Response Format

Produce the 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

  • Project structure follows separation of concerns
  • Middleware pipeline ordered by responsibility
  • Error handler catches all exceptions
  • Validation integrated with schema library

Max Response Length

4096 tokens

Workflow

Step 1: Select Framework

| Framework | Performance | Ecosystem | TypeScript | When | |---|---|---|---|---| | Express | Moderate | Largest | Manual setup | Large ecosystem, legacy, most devs know it | | Fastify | High | Large | Native | Performance-critical, JSON schema validation | | Hono | Very High | Growing | Native | Edge, Bun, minimal footprint |

Step 2: Set Up Project Structure

src/
+-- modules/
|   +-- orders/
|   |   +-- order.controller.ts
|   |   +-- order.service.ts
|   |   +-- order.repository.ts
|   |   +-- order.schema.ts        # Zod validation
|   |   +-- order.routes.ts
|   |   +-- order.test.ts
|   |   +-- order.mapper.ts
|   +-- products/
|   |   +-- ...
|   +-- users/
|       +-- ...
+-- common/
|   +-- middleware/
|   |   +-- auth.ts
|   |   +-- error-handler.ts
|   |   +-- request-logger.ts
|   |   +-- rate-limiter.ts
|   |   +-- request-id.ts
|   |   +-- validate.ts
|   +-- errors/
|   |   +-- app-error.ts
|   |   +-- not-found.ts
|   +-- types/
|   |   +-- index.ts
|   |   +-- express.d.ts
+-- config/
|   +-- database.ts
|   +-- env.ts
|   +-- redis.ts
|   +-- logger.ts
+-- app.ts              # Express/Fastify app setup
+-- server.ts           # Entry point

Step 3: Order Middleware Pipeline (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 { requestId } from './common/middleware/request-id';
import { routes } from './routes';

const app = express();

app.use(cors({ origin: process.env.CORS_ORIGIN, credentials: true }));
app.use(helmet());
app.use(compression());
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true }));
app.use(requestId);
app.use(requestLogger);
app.use(rateLimiter);
app.use('/api/v1', routes);
app.use(notFoundHandler);
app.use(errorHandler);

For Fastify:

import Fastify from 'fastify';
import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import compress from '@fastify/compress';
import rateLimit from '@fastify/rate-limit';

const app = Fastify({ logger: true });

await app.register(cors, { origin: process.env.CORS_ORIGIN });
await app.register(helmet);
await app.register(compress);
await app.register(rateLimit, { max: 100, timeWindow: '1 minute' });
await app.register(routes, { prefix: '/api/v1' });

app.setNotFoundHandler((req, reply) => {
  reply.status(404).send({ success: false, error: { code: 'NOT_FOUND', message: 'Route not found' } });
});

app.setErrorHandler((err, req, reply) => {
  const status = err.statusCode || 500;
  reply.status(status).send({ success: false, error: { code: err.code || 'INTERNAL', message: err.message } });
});

Step 4: Implement Error Handling

// common/errors/app-error.ts
export interface ErrorDetails {
  code: string;
  message: string;
  details?: unknown;
  stack?: string;
}

export class AppError extends Error {
  public readonly statusCode: number;
  public readonly code: string;
  public readonly details: unknown;
  public readonly isOperational: boolean;

  constructor(statusCode: number, code: string, message: string, details?: unknown) {
    super(message);
    this.statusCode = statusCode;
    this.code = code;
    this.details = details;
    this.isOperational = true;
    Object.setPrototypeOf(this, new.target.prototype);
    Error.captureStackTrace(this, this.constructor);
  }

  public toJSON(): ErrorDetails {
    return {
      code: this.code,
      message: this.message,
      details: this.details,
      stack: process.env.NODE_ENV === 'development' ? this.stack : undefined,
    };
  }
}

export class NotFoundError extends AppError {
  constructor(entity: string, id: string) {
    super(404, 'NOT_FOUND', `${entity} with id ${id} not found`);
  }
}

export class ValidationError extends AppError {
  constructor(errors: unknown) {
    super(400, 'VALIDATION', 'Validation failed', errors);
  }
}

export class UnauthorizedError extends AppError {
  constructor(message = 'Unauthorized') {
    super(401, 'UNAUTHORIZED', message);
  }
}

export class ForbiddenError extends AppError {
  constructor(message = 'Forbidden') {
    super(403, 'FORBIDDEN', message);
  }
}

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

export function errorHandler(err: Error, req: Request, res: Response, _next: NextFunction): void {
  if (err instanceof AppError) {
    logger.warn({ err, requestId: req.id, path: req.path }, 'Operational error');
    res.status(err.statusCode).json({
      success: false,
      error: err.toJSON(),
    });
    return;
  }

  logger.error({ err, requestId: req.id, path: req.path }, 'Unhandled error');
  res.status(500).json({
    success: false,
    error: {
      code: 'INTERNAL_ERROR',
      message: process.env.NODE_ENV === 'production' ? 'An unexpected error occurred' : err.message,
    },
  });
}

Step 5: Validation with Zod

// modules/orders/order.schema.ts
import { z } from 'zod';

export const createOrderSchema = z.object({
  customerId: z.string().uuid(),
  items: z.array(z.object({
    productId: z.string().uuid(),
    quantity: z.number().int().positive(),
    unitPrice: z.number().positive(),
  })).min(1, 'At least one item required'),
  shippingAddress: z.object({
    street: z.string().min(1),
    city: z.string().min(1),
    zipCode: z.string().regex(/^\d{5}(-\d{4})?$/),
    country: z.string().length(2),
  }),
  couponCode: z.string().optional(),
});

export type CreateOrderInput = z.infer;

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

export function validate(schema: ZodSchema, source: 'body' | 'query' | 'params' = 'body') {
  return (req: Request, _res: Response, next: NextFunction): void => {
    const result = schema.safeParse(req[source]);
    if (result.success) {
      req[source] = result.data;
      next();
    } else {
      next(new ValidationError(result.error.issues));
    }
  };
}

// Usage in route
router.post('/', validate(createOrderSchema), orderController.create);

Step 6: Routing and Controller Pattern

// modules/orders/order.routes.ts
import { Router } from 'express';
import { OrderController } from './order.controller';
import { validate } from '../../common/middleware/validate';
import { createOrderSchema, updateOrderSchema } from './order.schema';
import { authenticate } from '../../common/middleware/auth';

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

router.use(authenticate);

router.get('/', controller.list);
router.get('/:id', controller.getById);
router.post('/', validate(createOrderSchema), controller.create);
router.put('/:id', validate(updateOrderSchema), controller.update);
router.delete('/:id', controller.delete);

export { router as orderRoutes };

// modules/orders/order.controller.ts
import type { Request, Response, NextFunction } from 'express';
import { OrderService } from './order.service';

export class OrderController {
  constructor(private readonly orderService = new OrderService()) {}

  list = async (req: Request, res: Response, next: NextFunction): Promise => {
    try {
      const result = await this.orderService.findAll(req.query);
      res.json({ success: true, data: result });
    } catch (err) {
      next(err);
    }
  };

  getById = async (req: Request, res: Response, next: NextFunction): Promise => {
    try {
      const result = await this.orderService.findById(req.params.id);
      res.json({ success: true, data: result });
    } catch (err) {
      next(err);
    }
  };

  create = async (req: Request, res: Response, next: NextFunction): Promise => {
    try {
      const result = await this.orderService.create(req.body);
      res.status(201).json({ success: true, data: result });
    } catch (err) {
      next(err);
    }
  };

  update = async (req: Request, res: Response, next: NextFunction): Promise => {
    try {
      const result = await this.orderService.update(req.params.id, req.body);
      res.json({ success: true, data: result });
    } catch (err) {
      next(err);
    }
  };

  delete = async (req: Request, res: Response, next: NextFunction): Promise => {
    try {
      await this.orderService.delete(req.params.id);
      res.status(204).send();
    } catch (err) {
      next(err);
    }
  };
}

Step 7: Configuration Management

// config/env.ts
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(),
  REDIS_URL: z.string().url().optional(),
  JWT_SECRET: z.string().min(32),
  JWT_EXPIRES_IN: z.string().default('15m'),
  CORS_ORIGIN: z.string().default('http://localhost:3000'),
  LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
});

export type Env = z.infer;

let env: Env;

export function loadEnv(): Env {
  const result = envSchema.safeParse(process.env);
  if (!result.success) {
    console.error('Invalid environment variables:', result.error.issues);
    process.exit(1);
  }
  env = result.data;
  return env;
}

export function getEnv(): Env {
  if (!env) return loadEnv();
  return env;
}

Step 8: Dependency Injection Container

// config/container.ts
import { OrderService } from '../modules/orders/order.service';
import { OrderRepository } from '../modules/orders/order.repository';
import { PaymentService } from '../modules/payments/payment.service';
import { NotificationService } from '../modules/notifications/notification.service';
import { Database } from './database';

export class Container {
  private static instance: Container;
  private readonly db: Database;
  private readonly services = new Map();

  private constructor() {
    this.db = new Database();
  }

  static getInstance(): Container {
    if (!Container.instance) {
      Container.instance = new Container();
    }
    return Container.instance;
  }

  getOrderRepository(): OrderRepository {
    if (!this.services.has('orderRepository')) {
      this.services.set('orderRepository', new OrderRepository(this.db));
    }
    return this.services.get('orderRepository') as OrderRepository;
  }

  getOrderService(): OrderService {
    if (!this.services.has('orderService')) {
      this.services.set('orderService', new OrderService(
        this.getOrderRepository(),
        this.getPaymentService(),
        this.getNotificationService(),
      ));
    }
    return this.services.get('orderService') as OrderService;
  }

  getPaymentService(): PaymentService {
    if (!this.services.has('paymentService')) {
      this.services.set('paymentService', new PaymentService());
    }
    return this.services.get('paymentService') as PaymentService;
  }

  getNotificationService(): NotificationService {
    if (!this.services.has('notificationService')) {
      this.services.set('notificationService', new NotificationService());
    }
    return this.services.get('notificationService') as NotificationService;
  }
}

Step 9: Testing Setup

// modules/orders/order.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import request from 'supertest';
import { createApp } from '../app';
import { OrderService } from './order.service';

vi.mock('./order.service');

describe('Orders API', () => {
  let app: Express.Application;

  beforeEach(() => {
    vi.clearAllMocks();
    app = createApp('test');
  });

  describe('POST /api/v1/orders', () => {
    it('creates order successfully', async () => {
      const mockOrder = { id: '123', customerId: 'cust-1', items: [] };
      vi.mocked(OrderService.prototype.create).mockResolvedValue(mockOrder);

      const res = await request(app)
        .post('/api/v1/orders')
        .send({
          customerId: '550e8400-e29b-41d4-a716-446655440000',
          items: [{ productId: '550e8400-e29b-41d4-a716-446655440001', quantity: 2, unitPrice: 19.99 }],
          shippingAddress: { street: '123 Main', city: 'NYC', zipCode: '10001', country: 'US' },
        })
        .expect(201);

      expect(res.body.success).toBe(true);
      expect(res.body.data.id).toBe('123');
    });

    it('returns 400 for invalid input', async () => {
      const res = await request(app)
        .post('/api/v1/orders')
        .send({ customerId: 'invalid' })
        .expect(400);

      expect(res.body.error.code).toBe('VALIDATION');
    });

    it('returns 401 without auth token', async () => {
      const res = await request(app)
        .post('/api/v1/orders')
        .send({})
        .expect(401);
    });
  });

  describe('GET /api/v1/orders/:id', () => {
    it('returns 404 for non-existent order', async () => {
      vi.mocked(OrderService.prototype.findById).mockRejectedValue(
        new NotFoundError('Order', '999')
      );

      await request(app)
        .get('/api/v1/orders/999')
        .expect(404);
    });
  });
});

Architecture Decision Trees

Framework Selection

Need high performance?
  +-- Yes -> Need edge runtime?
  |   +-- Yes -> Hono (Bun, Deno, Cloudflare Workers)
  |   +-- No  -> Fastify (JSON schema validation, high throughput)
  +-- No  -> Need largest ecosystem?
      +-- Yes -> Express (most middleware, community packages)
      +-- No  -> Hono (modern, lightweight, good DX)

Request Validation Strategy

TypeScript project?
  +-- Yes -> Zod (type inference, composable schemas)
  +-- No  -> Joi (mature, expressive API)

Project Structure Decision

Monorepo with shared types?
  +-- Yes -> Use Nx or Turborepo, shared packages for types/schemas
  +-- No  -> Flat modules/ structure with domain grouping

Error Handling Approach

Single service deployment?
  +-- Yes -> Centralized AppError hierarchy with status codes
  +-- No  -> Add correlation IDs, distributed tracing headers

Common Pitfalls

  1. Middleware ordering wrong: Rate limiter before JSON parser causes body read issues. Security middleware (helmet, cors) must come first, before any body parsing.
  1. Swallowing async errors: Express does not catch async promise rejections automatically. Always use express-async-errors

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.