Agent Skillsalinaqi/maggy › nodejs-backend

nodejs-backend

GitHub

提供Node.js后端开发的最佳实践,涵盖Express/Fastify框架的项目结构、API路由设计、依赖注入及错误处理模式。

skills/nodejs-backend/SKILL.md alinaqi/maggy

Trigger Scenarios

需要构建Node.js后端服务 询问后端项目架构或目录规范 涉及Express或Fastify的路由与中间件实现

Install

npx skills add alinaqi/maggy --skill nodejs-backend -g -y
More Options

Use without installing

npx skills use alinaqi/maggy@nodejs-backend

指定 Agent (Claude Code)

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

安装 repo 全部 skill

npx skills add alinaqi/maggy --all -g -y

预览 repo 内 skill

npx skills add alinaqi/maggy --list

SKILL.md

Frontmatter
{
    "name": "nodejs-backend",
    "paths": [
        "src\/api\/**",
        "src\/routes\/**",
        "src\/server\/**",
        "src\/middleware\/**",
        "server\/**",
        "api\/**"
    ],
    "effort": "medium",
    "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
}

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

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

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

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

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

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

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

// 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)

// 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: 'test@example.com' }),
      findByEmail: jest.fn().mockResolvedValue(null),
    };

    const result = await createUser({ email: 'test@example.com', name: 'Test' }, mockRepo);

    expect(result.email).toBe('test@example.com');
    expect(mockRepo.create).toHaveBeenCalledTimes(1);
  });
});

Integration Tests (API)

// 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: 'new@example.com', name: 'New User' });

    expect(response.status).toBe(201);
    expect(response.body.email).toBe('new@example.com');
  });
});

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

Version History

  • bb6195f Current 2026-07-25 07:43

Same Skill Collection

skills/aeo-optimization/SKILL.md
skills/agent-teams/SKILL.md
skills/agentic-development/SKILL.md
skills/ai-models/SKILL.md
skills/android-java/SKILL.md
skills/android-kotlin/SKILL.md
skills/aws-aurora/SKILL.md
skills/aws-dynamodb/SKILL.md
skills/azure-cosmosdb/SKILL.md
skills/base/SKILL.md
skills/cloudflare-d1/SKILL.md
skills/code-deduplication/SKILL.md
skills/code-graph/SKILL.md
skills/codex-review/SKILL.md
skills/commit-hygiene/SKILL.md
skills/council-review/SKILL.md
skills/cpg-analysis/SKILL.md
skills/cross-agent-delegation/SKILL.md
skills/database-schema/SKILL.md
skills/existing-repo/SKILL.md
skills/firebase/SKILL.md
skills/flutter/SKILL.md
skills/gemini-review/SKILL.md
skills/icpg/SKILL.md
skills/iterative-development/SKILL.md
skills/klaviyo/SKILL.md
skills/llm-patterns/SKILL.md
skills/maggy/SKILL.md
skills/medusa/SKILL.md
skills/mnemos/SKILL.md
skills/ms-teams-apps/SKILL.md
skills/playwright-testing/SKILL.md
skills/polyphony/SKILL.md
skills/project-tooling/SKILL.md
skills/pwa-development/SKILL.md
skills/python/SKILL.md
skills/react-native/SKILL.md
skills/react-web/SKILL.md
skills/reddit-api/SKILL.md
skills/session-management/SKILL.md
skills/shopify-apps/SKILL.md
skills/site-architecture/SKILL.md
skills/supabase/SKILL.md
skills/team-coordination/SKILL.md
skills/ticket-craft/SKILL.md
skills/ui-mobile/SKILL.md
skills/ui-testing/SKILL.md
skills/ui-web/SKILL.md
skills/user-journeys/SKILL.md

Metadata

Files
0
Version
abc600e
Hash
a728c7fd
Indexed
2026-07-25 07:43

inicio - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-20 05:28
浙ICP备14020137号-1 $mapa de visitantes$