Agent skill
backend-nodejs
Modern Node.js backend stack: NestJS/Hono, Drizzle ORM, Vitest, TypeScript. Use when: building Node.js APIs, TypeScript backends, or serverless functions. Triggers: "nestjs", "node backend", "express", "hono", "drizzle", "vitest", "typescript api", "node api", "bun".
Install this agent skill to your Project
npx add-skill https://github.com/timequity/vibe-coder/tree/main/skills/backend-nodejs
SKILL.md
Node.js Backend Stack
Live docs: Add
use context7to prompt for up-to-date Hono, Drizzle, Zod documentation.
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 |
|---|---|
npm |
pnpm |
| Jest | Vitest |
| Prisma (heavy) | Drizzle (lightweight) |
| Express (old) | Hono (modern, fast) |
.eslintrc |
eslint.config.js (flat config) |
| CommonJS | ESM ("type": "module") |
any types |
Proper TypeScript types |
| Callback patterns | async/await |
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
mvp-help
Help and documentation for Idea to MVP plugin. Use when: user asks about building MVPs, vibe coding, or available commands. Triggers: "help", "what can you do", "mvp help", "how to build".
verification-gate
Hidden quality gate that runs before showing "Done!" to user - ensures all tests pass, build succeeds, and requirements met before claiming completion
brainstorming
Refine ideas into detailed designs through Socratic dialogue. Use when: user has rough idea, needs to clarify requirements, explore approaches. Triggers: "brainstorm", "discuss idea", "I'm thinking about", "what if", "help me think through", "explore options", "/brainstorm".
subagent-creator
Guide for creating effective subagents (custom agents). Use when users want to create a new subagent that can be dispatched via Task tool for autonomous work. Covers frontmatter fields (name, description, tools, model, permissionMode, skills), prompt design, and when to use subagents vs skills.
backend-rust
Modern Rust backend with Axum, SQLx, tokio + CI/CD automation. Use when: building Rust APIs, high-performance services, or needing build/test/lint/audit automation. Triggers: "axum", "rust backend", "rust api", "sqlx", "tokio", "cargo build", "cargo test", "clippy", "rustfmt", "cargo-audit", "cross-compile", "rust ci", "release build", "rust security", "shuttle", "actix".
test-driven-development
Write failing test first, then minimal code to pass. Red-Green-Refactor cycle. Use when: implementing features, fixing bugs, refactoring code. Triggers: "implement", "add feature", "fix bug", "tdd", "test first", "write tests", "test-driven".
Didn't find tool you were looking for?