Skip to main content
skill CX Codex

nodejs-backend — Claude Code skill: Node.js backend patterns

A skill that lays out Node.js project structure and ready-made patterns for Express, Fastify and repositories. It kicks in when the agent edits code under src/api, src/routes or src/server.

Install into Claude Code · Skills CLI (Vercel) All methods and platforms ↓
npx skills add alinaqi/maggy --skill nodejs-backend -a claude-code
Open on GitHub

What nodejs-backend does

The skill describes a layered layout: core for pure business logic, infra for HTTP, database and third-party APIs, plus a separate config. It ships code samples the agent copies from — a route handler with zod validation, dependency injection at the composition root, domain error classes and a global Express error handler.

For data access it shows a repository pattern on Kysely and how to run transactions. Config is loaded through a zod schema that validates environment variables. Tests split into unit tests of the core with a mocked repository and integration tests through supertest.

Activation is path-based: src/api/**, src/routes/**, src/server/**, src/middleware/**, server/**, api/**. Keep your routes somewhere else and the skill will not load on its own — you call it by hand. The samples assume zod, Kysely and supertest, so on a different stack expect to rewrite them.

What it helps with

  • split a Node.js backend into core, infra and config layers
  • write an Express route handler with zod body validation
  • build a repository and transactions on Kysely
  • collect domain errors behind one global Express handler
  • validate environment variables with a schema at startup

Example prompts for the agent

  • add a POST /users route following the nodejs-backend skill
  • create an orders repository on Kysely like in nodejs-backend
  • set up the global Express error handler from the skill

How to install nodejs-backend

1Skills CLI (Vercel)

npx skills add alinaqi/maggy --skill nodejs-backend -a claude-code

2Manually: put the files in place

~/.claude/skills/nodejs-backend/SKILL.md

Copy the whole skill folder; for one project use .claude/skills/nodejs-backend/

Step by step, with commands for each platform: How to install skills in Claude Code: five ways

Questions about nodejs-backend

How do I install nodejs-backend in Claude?

Run npx skills add alinaqi/maggy --skill nodejs-backend -a claude-code. The skill file lands at ~/.claude/skills/nodejs-backend/SKILL.md.

What does nodejs-backend do?

It gives the agent Node.js backend patterns: project structure, Express and Fastify route handlers, repositories, transactions, config and tests. The agent follows them while writing API, route, server and middleware code.

Which libraries does the skill use?

The samples rely on zod for validation, Kysely for the database and supertest for integration tests. On another stack you will need to adapt them.

Source text

SKILL.md · MIT
---
name: nodejs-backend
description: Node.js backend patterns with Express/Fastify, repositories
when-to-use: When working on Node.js backend code - API routes, middleware, server setup
user-invocable: false
paths: ["src/api/**", "src/routes/**", "src/server/**", "src/middleware/**", "server/**", "api/**"]
effort: medium
---

# Node.js Backend Skill


---

## Project Structure

```
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
```

---

## API Design

### Route Handler Pattern
```typescript
// 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;
}
```

### Dependency Injection at Composition Root
```typescript
// 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);
}
```

---

## Error Handling

### Domain Errors
```typescript
// 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);
  }
}
```

### Global Error Handler
```typescript
// 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' },
  });
};
```

---

## Database Patterns

### Repository Pattern
```typescript
// 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();
  }
}
```

### Transactions
```typescript
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();
  });
}
```

---

## Configuration

### Validated Config
```typescript
// 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);
}
```

---

## Testing

### Unit Tests (Core)
```typescript
// 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);
  });
});
```

### Integration Tests (API)
```typescript
// 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]');
  });
});
```

---

## Node.js Anti-Patterns

- ❌ Callback hell - use async/await
- ❌ Unhandled promise rejections - always catch or let error handler catch
- ❌ Blocking the event loop - offload heavy computation
- ❌ Secrets in code - use environment variables
- ❌ SQL string concatenation - use parameterized queries
- ❌ No input validation - validate at API boundary
- ❌ Console.log in production - use proper logger
- ❌ No graceful shutdown - handle SIGTERM
- ❌ Monolithic route files - split by resource

© alinaqi · MIT · alinaqi/maggy

Similar skills

Everything for Backend and APIs →

From the same set32

react-native A skill that sets project structure, component patterns and testing rules for React Native. Kicks in when the agent touches .tsx files or iOS and Android code. CX Codex +1 566installs aws-dynamodb Guides coding assistants through DynamoDB single-table design, GSI indexing patterns, and AWS SDK v3 operations in TypeScript and Python. CX Codex +1 522installs klaviyo A skill for wiring Klaviyo into your code: customer profiles, event tracking, campaigns, flows and segmentation. Useful when a store needs marketing hooks. CX Codex +1 517installs aeo-optimization Formats content and page structures for citations across ChatGPT, Perplexity, and Google AI Overviews. Useful when writing documentation or product landing pages. CX Codex +1 437installs security Sets up preventive security rules, blocks secrets from leaking into client bundles, and configures pre-commit hooks. Useful when handling auth, user input, or API keys. CX Codex +1 426installs agentic-development A skill for building autonomous AI agents with Pydantic AI in Python or the Claude Agent SDK in Node.js. Useful when an agent has to run multi-step tasks with tools. CX Codex +1 406installs ui-web Design skill enforcing WCAG 2.1 AA accessibility, Tailwind CSS tokens, dark mode contrast, and glassmorphic UI patterns. CX Codex +1 393installs woocommerce A skill for talking to WooCommerce over the REST API: products, orders, customers, webhooks. Useful when your agent writes store integration code. CX Codex +1 390installs

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor