Claude-skill-registry backend-nodejs
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/backend-nodejs" ~/.claude/skills/majiayu000-claude-skill-registry-backend-nodejs && rm -rf "$T"
manifest:
skills/data/backend-nodejs/SKILL.mdsource content
Node.js Backend Stack
Live docs: Add
to prompt for up-to-date Hono, Drizzle, Zod documentation.use context7
Quick Reference
| Topic | Reference |
|---|---|
| Testing | testing.md — Vitest patterns, mocking, API tests |
Tooling (2025)
| Tool | Purpose | Why |
|---|---|---|
| pnpm | Package manager | Fast, disk efficient |
| Vitest | Testing | Fast, ESM native, Jest compatible |
| Drizzle | ORM | Type-safe, lightweight, SQL-like |
| Hono | Web framework | Fast, lightweight, edge-ready |
| NestJS | Framework | Enterprise, DI, structured |
| Zod | Validation | Type inference, composable |
| ESLint 9 | Linting | Flat config, modern |
Project Setup
# Hono (lightweight) pnpm create hono@latest my-api cd my-api pnpm add drizzle-orm postgres zod pnpm add -D drizzle-kit vitest @types/node typescript # NestJS (enterprise) pnpm dlx @nestjs/cli new my-api pnpm add drizzle-orm postgres pnpm add -D drizzle-kit vitest
TypeScript Config
// tsconfig.json { "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "outDir": "dist", "rootDir": "src" }, "include": ["src"] }
ESLint 9 Flat Config
// eslint.config.js import js from '@eslint/js'; import typescript from '@typescript-eslint/eslint-plugin'; import tsParser from '@typescript-eslint/parser'; export default [ js.configs.recommended, { files: ['**/*.ts'], languageOptions: { parser: tsParser, parserOptions: { project: './tsconfig.json' }, }, plugins: { '@typescript-eslint': typescript }, rules: { ...typescript.configs.recommended.rules, '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], }, }, ];
Project Structure
src/ ├── index.ts # Entry point ├── config.ts # Environment config ├── db/ │ ├── index.ts # Drizzle client │ ├── schema.ts # Table definitions │ └── migrate.ts # Migration runner ├── routes/ │ ├── index.ts │ ├── auth.ts │ └── users.ts ├── services/ │ └── user.ts ├── middleware/ │ ├── auth.ts │ └── error.ts └── types/ └── index.ts tests/ ├── setup.ts └── users.test.ts drizzle/ └── migrations/
Hono Patterns
Basic App
import { Hono } from 'hono'; import { cors } from 'hono/cors'; import { logger } from 'hono/logger'; import { HTTPException } from 'hono/http-exception'; const app = new Hono(); app.use('*', logger()); app.use('*', cors()); app.get('/', (c) => c.json({ status: 'ok' })); app.onError((err, c) => { if (err instanceof HTTPException) { return c.json({ error: err.message }, err.status); } console.error(err); return c.json({ error: 'Internal Server Error' }, 500); }); export default app;
Route with Validation
import { Hono } from 'hono'; import { zValidator } from '@hono/zod-validator'; import { z } from 'zod'; const users = new Hono(); const createUserSchema = z.object({ email: z.string().email(), name: z.string().min(1).max(100), }); users.post('/', zValidator('json', createUserSchema), async (c) => { const data = c.req.valid('json'); const user = await userService.create(data); return c.json(user, 201); }); users.get('/:id', async (c) => { const id = c.req.param('id'); const user = await userService.findById(id); if (!user) { throw new HTTPException(404, { message: 'User not found' }); } return c.json(user); }); export default users;
Auth Middleware
import { createMiddleware } from 'hono/factory'; import { HTTPException } from 'hono/http-exception'; import { verify } from 'hono/jwt'; type Env = { Variables: { userId: string }; }; export const authMiddleware = createMiddleware<Env>(async (c, next) => { const header = c.req.header('Authorization'); if (!header?.startsWith('Bearer ')) { throw new HTTPException(401, { message: 'Missing token' }); } const token = header.slice(7); try { const payload = await verify(token, process.env.JWT_SECRET!); c.set('userId', payload.sub as string); await next(); } catch { throw new HTTPException(401, { message: 'Invalid token' }); } });
Drizzle ORM
Schema Definition
// src/db/schema.ts import { pgTable, serial, varchar, timestamp, integer } from 'drizzle-orm/pg-core'; export const users = pgTable('users', { id: serial('id').primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), name: varchar('name', { length: 100 }).notNull(), createdAt: timestamp('created_at').defaultNow().notNull(), }); export const posts = pgTable('posts', { id: serial('id').primaryKey(), userId: integer('user_id').references(() => users.id).notNull(), title: varchar('title', { length: 255 }).notNull(), body: varchar('body', { length: 10000 }), createdAt: timestamp('created_at').defaultNow().notNull(), }); // Types export type User = typeof users.$inferSelect; export type NewUser = typeof users.$inferInsert;
Database Client
// src/db/index.ts import { drizzle } from 'drizzle-orm/postgres-js'; import postgres from 'postgres'; import * as schema from './schema'; const client = postgres(process.env.DATABASE_URL!); export const db = drizzle(client, { schema });
Queries
import { db } from '@/db'; import { users, posts } from '@/db/schema'; import { eq, desc } from 'drizzle-orm'; // Insert const [user] = await db.insert(users) .values({ email: 'test@example.com', name: 'Test' }) .returning(); // Select const user = await db.query.users.findFirst({ where: eq(users.email, 'test@example.com'), }); // Select with relations const userWithPosts = await db.query.users.findFirst({ where: eq(users.id, userId), with: { posts: true }, }); // Update await db.update(users) .set({ name: 'New Name' }) .where(eq(users.id, userId)); // Delete await db.delete(users).where(eq(users.id, userId)); // Pagination const usersList = await db.select() .from(users) .orderBy(desc(users.createdAt)) .limit(20) .offset(0);
Migrations
// drizzle.config.ts import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/db/schema.ts', out: './drizzle/migrations', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL!, }, });
# Generate migration pnpm drizzle-kit generate # Apply migrations pnpm drizzle-kit migrate # Studio (GUI) pnpm drizzle-kit studio
Config with Zod
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(), JWT_SECRET: z.string().min(32), }); export const env = envSchema.parse(process.env);
Anti-patterns
| Don't | Do Instead |
|---|---|
| |
| Jest | Vitest |
| Prisma (heavy) | Drizzle (lightweight) |
| Express (old) | Hono (modern, fast) |
| (flat config) |
| CommonJS | ESM () |
types | Proper TypeScript types |
| Callback patterns | async/await |