Guides Node.js backend development with Express/Fastify using repository pattern, dependency injection at composition root, and layered core/infra architecture. Includes route handlers, middleware, and error handling patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/alinaqi-claude-bootstrap:nodejs-backendWhen to use
When working on Node.js backend code - API routes, middleware, server setup
src/api/**src/routes/**src/server/**src/middleware/**server/**api/**The summary Claude sees in its skill listing — used to decide when to auto-load this skill
---
project/
├── src/
│ ├── core/ # Pure business logic
│ │ ├── types.ts # Domain types
│ │ ├── errors.ts # Domain errors
│ │ └── services/ # Pure functions
│ │ ├── user.ts
│ │ └── order.ts
│ ├── infra/ # Side effects
│ │ ├── http/ # HTTP layer
│ │ │ ├── server.ts # Server setup
│ │ │ ├── routes/ # Route handlers
│ │ │ └── middleware/ # Express middleware
│ │ ├── db/ # Database
│ │ │ ├── client.ts # DB connection
│ │ │ ├── repositories/ # Data access
│ │ │ └── migrations/ # Schema migrations
│ │ └── external/ # Third-party APIs
│ ├── config/ # Configuration
│ │ └── index.ts # Env vars, validated
│ └── index.ts # Entry point
├── tests/
│ ├── unit/
│ └── integration/
├── package.json
└── CLAUDE.md
// routes/users.ts
import { Router } from 'express';
import { z } from 'zod';
import { createUser } from '../../core/services/user';
import { UserRepository } from '../db/repositories/user';
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export function createUserRoutes(userRepo: UserRepository): Router {
const router = Router();
router.post('/', async (req, res, next) => {
try {
const input = CreateUserSchema.parse(req.body);
const user = await createUser(input, userRepo);
res.status(201).json(user);
} catch (error) {
next(error);
}
});
return router;
}
// index.ts
import { createApp } from './infra/http/server';
import { createDbClient } from './infra/db/client';
import { UserRepository } from './infra/db/repositories/user';
import { createUserRoutes } from './infra/http/routes/users';
async function main(): Promise<void> {
const db = await createDbClient();
const userRepo = new UserRepository(db);
const app = createApp({
userRoutes: createUserRoutes(userRepo),
});
app.listen(3000);
}
// core/errors.ts
export class DomainError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number = 400
) {
super(message);
this.name = 'DomainError';
}
}
export class NotFoundError extends DomainError {
constructor(resource: string, id: string) {
super(`${resource} with id ${id} not found`, 'NOT_FOUND', 404);
}
}
export class ValidationError extends DomainError {
constructor(message: string) {
super(message, 'VALIDATION_ERROR', 400);
}
}
// middleware/errorHandler.ts
import { ErrorRequestHandler } from 'express';
import { DomainError } from '../../core/errors';
import { ZodError } from 'zod';
export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
if (err instanceof DomainError) {
return res.status(err.statusCode).json({
error: { code: err.code, message: err.message },
});
}
if (err instanceof ZodError) {
return res.status(400).json({
error: { code: 'VALIDATION_ERROR', details: err.errors },
});
}
console.error('Unexpected error:', err);
return res.status(500).json({
error: { code: 'INTERNAL_ERROR', message: 'Something went wrong' },
});
};
// db/repositories/user.ts
import { Kysely } from 'kysely';
import { Database, User } from '../types';
export class UserRepository {
constructor(private db: Kysely<Database>) {}
async findById(id: string): Promise<User | null> {
return this.db
.selectFrom('users')
.where('id', '=', id)
.selectAll()
.executeTakeFirst() ?? null;
}
async create(data: Omit<User, 'id' | 'createdAt'>): Promise<User> {
return this.db
.insertInto('users')
.values(data)
.returningAll()
.executeTakeFirstOrThrow();
}
}
async function transferFunds(
fromId: string,
toId: string,
amount: number,
db: Kysely<Database>
): Promise<void> {
await db.transaction().execute(async (trx) => {
await trx
.updateTable('accounts')
.set((eb) => ({ balance: eb('balance', '-', amount) }))
.where('id', '=', fromId)
.execute();
await trx
.updateTable('accounts')
.set((eb) => ({ balance: eb('balance', '+', amount) }))
.where('id', '=', toId)
.execute();
});
}
// config/index.ts
import { z } from 'zod';
const ConfigSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
API_KEY: z.string().min(1),
});
export type Config = z.infer<typeof ConfigSchema>;
export function loadConfig(): Config {
return ConfigSchema.parse(process.env);
}
// tests/unit/services/user.test.ts
import { createUser } from '../../../src/core/services/user';
describe('createUser', () => {
it('creates user with valid data', async () => {
const mockRepo = {
create: jest.fn().mockResolvedValue({ id: '1', email: '[email protected]' }),
findByEmail: jest.fn().mockResolvedValue(null),
};
const result = await createUser({ email: '[email protected]', name: 'Test' }, mockRepo);
expect(result.email).toBe('[email protected]');
expect(mockRepo.create).toHaveBeenCalledTimes(1);
});
});
// tests/integration/users.test.ts
import request from 'supertest';
import { createTestApp, createTestDb } from '../helpers';
describe('POST /users', () => {
let app: Express;
let db: TestDb;
beforeAll(async () => {
db = await createTestDb();
app = createTestApp(db);
});
afterAll(async () => {
await db.destroy();
});
it('creates user and returns 201', async () => {
const response = await request(app)
.post('/users')
.send({ email: '[email protected]', name: 'New User' });
expect(response.status).toBe(201);
expect(response.body.email).toBe('[email protected]');
});
});
npx claudepluginhub alinaqi/maggyNode.js backend patterns: layered architecture, TypeScript, validation, error handling, security, observability, logging, metrics, deployment. Use when building REST APIs, REST endpoints, middleware, Express/Fastify/Hono/NestJS/Koa servers, tRPC procedures, Bun servers, or server-side TypeScript.
Node.js backend conventions: project layout, configuration, logging, routing, and error handling patterns. Apply when implementing or modifying Node.js backend code in projects matching the nodejs stack profile. Use this skill to: - Pick conventional file/folder structure for a new module. - Wire configuration through env vars correctly. - Use the project's existing logger style. - Implement error handling that matches the framework (Express/Fastify/Koa). - Follow async/await patterns consistently. Do NOT use this skill for: - Frontend code (React/Vue/RN have their own conventions). - NestJS-specific patterns (decorators, DI, modules) — nest-plugin owns those. - Database schema design (call out as a sub-task; npm-patterns covers package management, not DB).
Implements Node.js backend patterns with Express/Fastify for middleware, error handling, auth, DB integration, REST/GraphQL APIs, microservices, and WebSockets.