← Back to Skills Marketplace
wpank

Nodejs Patterns

by wpank · GitHub ↗ · v1.0.0
cross-platform ✓ Security Clean
2211
Downloads
3
Stars
14
Active Installs
1
Versions
Install in OpenClaw
/install nodejs-patterns
Description
WHAT: Production-ready Node.js backend patterns - Express/Fastify setup, layered architecture, middleware, error handling, validation, database integration, authentication, and caching. WHEN: User is building REST APIs, setting up Node.js servers, implementing authentication, integrating databases, adding validation/caching, or structuring backend applications. KEYWORDS: nodejs, node, express, fastify, typescript, api, rest, middleware, authentication, jwt, validation, zod, postgres, mongodb, redis, caching, rate limiting, error handling
README (SKILL.md)

Node.js Backend Patterns

Patterns for building scalable, maintainable Node.js backend applications with TypeScript.

NEVER

  • NEVER store secrets in code - Use environment variables, never hardcode credentials
  • NEVER skip input validation - Validate all input at the middleware layer with Zod/Joi
  • NEVER expose error details in production - Return generic messages, log details server-side
  • NEVER use any type - TypeScript types prevent runtime errors
  • NEVER skip error handling - Always wrap async handlers, use global error middleware
  • NEVER use sync operations - Use async/await for I/O, never fs.readFileSync in handlers
  • NEVER trust client input - Sanitize, validate, and parameterize all queries

When to Use

  • Building REST APIs with Express or Fastify
  • Setting up middleware pipelines and error handling
  • Implementing authentication and authorization
  • Integrating databases with connection pooling and transactions
  • Adding validation, caching, and rate limiting

Project Structure — Layered Architecture

src/
├── controllers/     # Handle HTTP requests/responses
├── services/        # Business logic
├── repositories/    # Data access layer
├── models/          # Data models and types
├── middleware/      # Auth, validation, logging, errors
├── routes/          # Route definitions
├── config/          # Database, cache, env configuration
└── utils/           # Helpers, custom errors, response formatting

Controllers handle HTTP concerns, services contain business logic, repositories abstract data access. Each layer only calls the layer below it.

Express Setup

import express from "express";
import helmet from "helmet";
import cors from "cors";
import compression from "compression";

const app = express();

app.use(helmet());
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") }));
app.use(compression());
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));

Fastify Setup

import Fastify from "fastify";
import helmet from "@fastify/helmet";
import cors from "@fastify/cors";

const fastify = Fastify({
  logger: { level: process.env.LOG_LEVEL || "info" },
});

await fastify.register(helmet);
await fastify.register(cors, { origin: true });

// Type-safe routes with built-in schema validation
fastify.post\x3C{ Body: { name: string; email: string } }>(
  "/users",
  {
    schema: {
      body: {
        type: "object",
        required: ["name", "email"],
        properties: {
          name: { type: "string", minLength: 1 },
          email: { type: "string", format: "email" },
        },
      },
    },
  },
  async (request) => {
    const { name, email } = request.body;
    return { id: "123", name };
  },
);

Error Handling

Custom Error Classes

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

export class ValidationError extends AppError {
  constructor(message: string, public errors?: any[]) { super(message, 400); }
}
export class NotFoundError extends AppError {
  constructor(message = "Resource not found") { super(message, 404); }
}
export class UnauthorizedError extends AppError {
  constructor(message = "Unauthorized") { super(message, 401); }
}
export class ForbiddenError extends AppError {
  constructor(message = "Forbidden") { super(message, 403); }
}

Global Error Handler

import { Request, Response, NextFunction } from "express";
import { AppError, ValidationError } from "../utils/errors";

export const errorHandler = (
  err: Error, req: Request, res: Response, next: NextFunction,
) => {
  if (err instanceof AppError) {
    return res.status(err.statusCode).json({
      status: "error",
      message: err.message,
      ...(err instanceof ValidationError && { errors: err.errors }),
    });
  }

  // Don't leak details in production
  const message = process.env.NODE_ENV === "production"
    ? "Internal server error"
    : err.message;

  res.status(500).json({ status: "error", message });
};

// Wrap async route handlers to forward errors
export const asyncHandler = (
  fn: (req: Request, res: Response, next: NextFunction) => Promise\x3Cany>,
) => (req: Request, res: Response, next: NextFunction) => {
  Promise.resolve(fn(req, res, next)).catch(next);
};

Validation Middleware (Zod)

import { AnyZodObject, ZodError } from "zod";

export const validate = (schema: AnyZodObject) => {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      await schema.parseAsync({
        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 ValidationError("Validation failed", errors));
      } else {
        next(error);
      }
    }
  };
};

// Usage
import { z } from "zod";
const createUserSchema = z.object({
  body: z.object({
    name: z.string().min(1),
    email: z.string().email(),
    password: z.string().min(8),
  }),
});
router.post("/users", validate(createUserSchema), userController.createUser);

Authentication — JWT

Auth Middleware

import jwt from "jsonwebtoken";

interface JWTPayload { userId: string; email: string; }

export const authenticate = async (
  req: Request, res: Response, next: NextFunction,
) => {
  try {
    const token = req.headers.authorization?.replace("Bearer ", "");
    if (!token) throw new UnauthorizedError("No token provided");

    req.user = jwt.verify(token, process.env.JWT_SECRET!) as JWTPayload;
    next();
  } catch {
    next(new UnauthorizedError("Invalid token"));
  }
};

export const authorize = (...roles: string[]) => {
  return (req: Request, res: Response, next: NextFunction) => {
    if (!req.user) return next(new UnauthorizedError("Not authenticated"));
    if (!roles.some((r) => req.user?.roles?.includes(r))) {
      return next(new ForbiddenError("Insufficient permissions"));
    }
    next();
  };
};

Auth Service

export class AuthService {
  constructor(private userRepository: UserRepository) {}

  async login(email: string, password: string) {
    const user = await this.userRepository.findByEmail(email);
    if (!user || !(await bcrypt.compare(password, user.password))) {
      throw new UnauthorizedError("Invalid credentials");
    }

    return {
      token: jwt.sign(
        { userId: user.id, email: user.email },
        process.env.JWT_SECRET!,
        { expiresIn: "15m" },
      ),
      refreshToken: jwt.sign(
        { userId: user.id },
        process.env.REFRESH_TOKEN_SECRET!,
        { expiresIn: "7d" },
      ),
      user: { id: user.id, name: user.name, email: user.email },
    };
  }
}

Database Patterns

PostgreSQL Connection Pool

import { Pool, PoolConfig } from "pg";

const pool = new Pool({
  host: process.env.DB_HOST,
  port: parseInt(process.env.DB_PORT || "5432"),
  database: process.env.DB_NAME,
  user: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

pool.on("error", (err) => {
  console.error("Unexpected database error", err);
  process.exit(-1);
});

export const closeDatabase = async () => { await pool.end(); };

Transaction Pattern

async createOrder(userId: string, items: OrderItem[]) {
  const client = await this.db.connect();
  try {
    await client.query("BEGIN");

    const { rows } = await client.query(
      "INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id",
      [userId, calculateTotal(items)],
    );
    const orderId = rows[0].id;

    for (const item of items) {
      await client.query(
        "INSERT INTO order_items (order_id, product_id, quantity, price) VALUES ($1, $2, $3, $4)",
        [orderId, item.productId, item.quantity, item.price],
      );
      await client.query(
        "UPDATE products SET stock = stock - $1 WHERE id = $2",
        [item.quantity, item.productId],
      );
    }

    await client.query("COMMIT");
    return orderId;
  } catch (error) {
    await client.query("ROLLBACK");
    throw error;
  } finally {
    client.release();
  }
}

Rate Limiting

import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import Redis from "ioredis";

const redis = new Redis({ host: process.env.REDIS_HOST });

export const apiLimiter = rateLimit({
  store: new RedisStore({ client: redis, prefix: "rl:" }),
  windowMs: 15 * 60 * 1000,
  max: 100,
  standardHeaders: true,
  legacyHeaders: false,
});

export const authLimiter = rateLimit({
  store: new RedisStore({ client: redis, prefix: "rl:auth:" }),
  windowMs: 15 * 60 * 1000,
  max: 5,
  skipSuccessfulRequests: true,
});

Caching with Redis

import Redis from "ioredis";

const redis = new Redis({
  host: process.env.REDIS_HOST,
  retryStrategy: (times) => Math.min(times * 50, 2000),
});

export class CacheService {
  async get\x3CT>(key: string): Promise\x3CT | null> {
    const data = await redis.get(key);
    return data ? JSON.parse(data) : null;
  }

  async set(key: string, value: any, ttl?: number): Promise\x3Cvoid> {
    const serialized = JSON.stringify(value);
    ttl ? await redis.setex(key, ttl, serialized) : await redis.set(key, serialized);
  }

  async delete(key: string): Promise\x3Cvoid> { await redis.del(key); }

  async invalidatePattern(pattern: string): Promise\x3Cvoid> {
    const keys = await redis.keys(pattern);
    if (keys.length) await redis.del(...keys);
  }
}

API Response Helpers

export class ApiResponse {
  static success\x3CT>(res: Response, data: T, message?: string, statusCode = 200) {
    return res.status(statusCode).json({ status: "success", message, data });
  }

  static paginated\x3CT>(res: Response, data: T[], page: number, limit: number, total: number) {
    return res.json({
      status: "success",
      data,
      pagination: { page, limit, total, pages: Math.ceil(total / limit) },
    });
  }
}

Best Practices

  1. Use TypeScript — type safety prevents runtime errors
  2. Validate all input — Zod or Joi at the middleware layer
  3. Custom error classes — map to HTTP status codes, use global handler
  4. Never hardcode secrets — use environment variables
  5. Structured logging — Pino or Winston with request context
  6. Rate limiting — Redis-backed for distributed deployments
  7. Connection pooling — always for databases
  8. Dependency injection — constructor injection for testability
  9. Graceful shutdown — close DB pools, drain connections on SIGTERM
  10. Health checks/health endpoint for liveness/readiness probes
Usage Guidance
This skill appears to be a coherent collection of Node.js backend patterns and is instruction-only (no code executables). Before installing or copying snippets: (1) verify the skill source or author (homepage is missing and the README's install commands look nonstandard), (2) never paste production secrets into examples—use your project's env management, (3) if you plan to copy the provided code into a project, review and test it in a sandbox or branch, and (4) prefer installing dependencies from official package registries or trusted repositories rather than running unclear 'npx' commands that reference arbitrary URLs.
Capability Analysis
Type: OpenClaw Skill Name: nodejs-patterns Version: 1.0.0 The skill bundle is benign. The `SKILL.md` file provides comprehensive Node.js backend patterns and explicitly promotes secure coding practices, such as never hardcoding secrets, validating all input, and proper error handling. There are no indications of prompt injection attempts against the AI agent. The `README.md` contains standard installation instructions, including fetching the skill via `npx add` from a GitHub URL, which is a legitimate method for skill installation and does not show malicious intent or arbitrary code execution beyond the skill's purpose.
Capability Assessment
Purpose & Capability
The name/description (Node.js backend patterns) match the SKILL.md content: layered architecture, Express/Fastify snippets, error handling, validation, auth, DB, and caching. The skill does not request unrelated credentials, binaries, or config paths.
Instruction Scope
SKILL.md consists of code examples and guidance for Node.js servers only. It does not instruct the agent to read system files, transmit data externally, or access environment variables beyond typical use (process.env examples). No broad or open-ended instructions grant extra data access.
Install Mechanism
There is no install spec (instruction-only), which is low-risk. Minor oddities in the README installation examples (e.g., use of 'npx add' with a GitHub tree URL and references to local ~/.ai-skills paths) are unclear but do not indicate malicious behavior; they warrant verification before following.
Credentials
The skill does not declare any required environment variables or credentials. Inline examples reference process.env (typical for app config) but nothing in the SKILL.md instructs the agent to exfiltrate secrets or require unrelated tokens.
Persistence & Privilege
The skill is instruction-only, has no install steps that write to disk, and 'always' is false. It does not request elevated persistence or system-wide config changes.
How to Use
  1. Make sure OpenClaw is installed (local or Docker)
  2. Run the install command in chat: /install nodejs-patterns
  3. After installation, invoke the skill by name or use /nodejs-patterns
  4. Provide required inputs per the skill's parameter spec and get structured output
Version History
v1.0.0
Initial release — production-grade Node.js backend patterns. - Covers setup for Express and Fastify, including middleware and validation. - Layered architecture example for scalable code organization. - Standardized error handling with custom error classes and global handler. - Input validation best practices, including Zod middleware sample. - JWT authentication and role-based authorization middleware patterns. - Database integration guidance (PostgreSQL pool example included). - Emphasizes security and maintainability (“NEVER” rules outlined).
Metadata
Slug nodejs-patterns
Version 1.0.0
License
All-time Installs 14
Active Installs 14
Total Versions 1
Frequently Asked Questions

What is Nodejs Patterns?

WHAT: Production-ready Node.js backend patterns - Express/Fastify setup, layered architecture, middleware, error handling, validation, database integration, authentication, and caching. WHEN: User is building REST APIs, setting up Node.js servers, implementing authentication, integrating databases, adding validation/caching, or structuring backend applications. KEYWORDS: nodejs, node, express, fastify, typescript, api, rest, middleware, authentication, jwt, validation, zod, postgres, mongodb, redis, caching, rate limiting, error handling. It is an AI Agent Skill for Claude Code / OpenClaw, with 2211 downloads so far.

How do I install Nodejs Patterns?

Run "/install nodejs-patterns" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.

Is Nodejs Patterns free?

Yes, Nodejs Patterns is completely free (open-source). You can download, install and use it at no cost.

Which platforms does Nodejs Patterns support?

Nodejs Patterns is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).

Who created Nodejs Patterns?

It is built and maintained by wpank (@wpank); the current version is v1.0.0.

💬 Comments