Fastify AI Coding Rules
Fastify AI rules help engineering teams get better results from AI coding assistants like Cursor, Windsurf, and GitHub Copilot. By defining clear conventions for code style, architecture patterns, error handling, and module organisation, Fastify AI rules ensure that generated code is consistent, maintainable, and production-ready. Whether you are working on a side project or a large-scale enterprise system, community-curated rules on AI Rules Hub provide a solid foundation you can adopt instantly and customise to fit your team's standards.
Why Use AI Rules for Fastify?
- Ensure AI-generated Fastify code follows your team's conventions
- Prevent common anti-patterns that degrade maintainability
- Reduce code review cycles by getting AI output right the first time
- Standardise error handling, logging, and module structure
- Make AI assistants produce secure and performance-conscious code
Best Practices for Fastify AI Coding
Define a Consistent Code Style
Specify formatting preferences (indentation, quotes, trailing commas) for Fastify so AI output matches your linter configuration without manual edits.
Enforce Error Handling Patterns
Instruct AI to always handle errors explicitly, use structured logging, and avoid swallowing exceptions silently.
Set Module Organisation Rules
Define how Fastify modules should be organised — feature folders, barrel exports, and separation of concerns — so AI keeps the project structure clean.
Require Security-Conscious Patterns
Add rules that enforce input validation, sanitisation, and safe dependency usage so AI never introduces obvious security vulnerabilities.
Common Patterns & Standards
Separation of Concerns
Keep business logic, data access, and presentation layers separate in Fastify projects so each layer is independently testable.
Dependency Injection
Pass dependencies explicitly through constructors or function parameters — avoiding global state that makes testing difficult.
Consistent Naming Conventions
Rule AI to follow Fastify community naming standards for files, classes, functions, and constants.
Automated Testing Standards
Define what test types are required (unit, integration) and where test files should live so AI generates tests alongside implementation code.
Top Fastify Rules on AI Rules Hub
No description provided.
# Fastify + TypeScript + MongoDB Backend - Project Initialization Guide
> **AI Instruction**: Use this document to fully scaffold and initialize the backend project. Follow each section in order. Do not skip steps. Generate all files with the exact structure and content described.
---
## Tech Stack
| Layer | Technology |
|---|---|
| Runtime | Node.js (v20+) |
| Framework | Fastify v4 |
| Language | TypeScript (strict mode) |
| Database | MongoDB (via Mongoose v8) |
| Validation | Zod |
| Environment | dotenv |
| Dev Tooling | tsx, ts-node, eslint, prettier |
---
## Folder Structure
Generate the following folder and file structure exactly:
```
project-root/
├── src/
│ ├── config/
│ │ ├── db.ts # MongoDB connection logic
│ │ └── env.ts # Zod-validated environment variables
│ ├── modules/
│ │ └── example/
│ │ ├── example.controller.ts
│ │ ├── example.route.ts
│ │ ├── example.schema.ts # Zod schemas
│ │ ├── example.model.ts # Mongoose model
│ │ └── example.service.ts
│ ├── plugins/
│ │ ├── sensible.ts # fastify-sensible plugin
│ │ └── swagger.ts # Swagger/OpenAPI plugin (optional)
│ ├── hooks/
│ │ └── auth.hook.ts # Global auth hooks (if needed)
│ ├── utils/
│ │ ├── logger.ts # Custom logger wrapper
│ │ └── response.ts # Standard API response helpers
│ ├── types/
│ │ └── index.d.ts # Global type augmentations
│ ├── app.ts # Fastify app factory
│ └── server.ts # Entry point — starts server
├── .env
├── .env.example
├── .eslintrc.json
├── .prettierrc
├── tsconfig.json
├── package.json
└── README.md
```
---
## Step 1 — Initialize the Project
Run the following commands:
```bash
mkdir project-root && cd project-root
npm init -y
git init
```
---
## Step 2 — Install Dependencies
```bash
# Production dependencies
npm install fastify @fastify/sensible @fastify/cors mongoose zod dotenv
# Development dependencies
npm install -D typescript tsx ts-node @types/node \
eslint prettier eslint-config-prettier \
@typescript-eslint/parser @typescript-eslint/eslint-plugin
```
---
## Step 3 — TypeScript Configuration
**File: `tsconfig.json`**
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
```
---
## Step 4 — Package.json Scripts
Add the following `scripts` block to `package.json`:
```json
{
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"build": "tsc",
"start": "node dist/server.js",
"lint": "eslint src --ext .ts",
"format": "prettier --write src/**/*.ts"
}
}
```
---
## Step 5 — Environment Variables
**File: `.env.example`**
```env
NODE_ENV=development
PORT=3000
HOST=0.0.0.0
MONGODB_URI=mongodb://localhost:27017/mydb
JWT_SECRET=your_jwt_secret_here
```
**File: `.env`** — copy from `.env.example` and fill in real values.
---
## Step 6 — Zod Environment Validation
**File: `src/config/env.ts`**
```typescript
import { z } from 'zod';
import dotenv from 'dotenv';
dotenv.config();
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
PORT: z.coerce.number().default(3000),
HOST: z.string().default('0.0.0.0'),
MONGODB_URI: z.string().url(),
JWT_SECRET: z.string().min(16),
});
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
console.error('❌ Invalid environment variables:');
console.error(parsed.error.flatten().fieldErrors);
process.exit(1);
}
export const env = parsed.data;
```
---
## Step 7 — MongoDB Connection
**File: `src/config/db.ts`**
```typescript
import mongoose from 'mongoose';
import { env } from './env.js';
export async function connectDB(): Promise<void> {
try {
await mongoose.connect(env.MONGODB_URI);
console.log('✅ MongoDB connected');
} catch (error) {
console.error('❌ MongoDB connection error:', error);
process.exit(1);
}
}
export async function disconnectDB(): Promise<void> {
await mongoose.disconnect();
console.log('🔌 MongoDB disconnected');
}
```
---
## Step 8 — Fastify App Factory
**File: `src/app.ts`**
```typescript
import Fastify, { FastifyInstance } from 'fastify';
import sensible from '@fastify/sensible';
import cors from '@fastify/cors';
import { exampleRoutes } from './modules/example/example.route.js';
export async function buildApp(): Promise<FastifyInstance> {
const app = Fastify({
logger: {
level: 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true },
},
},
});
// Plugins
await app.register(sensible);
await app.register(cors, { origin: true });
// Routes
await app.register(exampleRoutes, { prefix: '/api/v1/examples' });
// Health check
app.get('/health', async () => ({ status: 'ok' }));
return app;
}
```
---
## Step 9 — Server Entry Point
**File: `src/server.ts`**
```typescript
import { buildApp } from './app.js';
import { connectDB, disconnectDB } from './config/db.js';
import { env } from './config/env.js';
async function main() {
const app = await buildApp();
await connectDB();
try {
await app.listen({ port: env.PORT, host: env.HOST });
console.log(`🚀 Server running at http://${env.HOST}:${env.PORT}`);
} catch (err) {
app.log.error(err);
await disconnectDB();
process.exit(1);
}
// Graceful shutdown
const shutdown = async (signal: string) => {
console.log(`\n⚠️ Received ${signal}. Shutting down...`);
await app.close();
await disconnectDB();
process.exit(0);
};
process.on('SIGINT', () => shutdown('SIGINT'));
process.on('SIGTERM', () => shutdown('SIGTERM'));
}
main();
```
---
## Step 10 — Example Module
### Zod Schema
**File: `src/modules/example/example.schema.ts`**
```typescript
import { z } from 'zod';
export const createExampleSchema = z.object({
name: z.string().min(1, 'Name is required').max(100),
description: z.string().optional(),
isActive: z.boolean().default(true),
});
export const updateExampleSchema = createExampleSchema.partial();
export const exampleParamsSchema = z.object({
id: z.string().length(24, 'Invalid MongoDB ObjectId'),
});
export type CreateExampleInput = z.infer<typeof createExampleSchema>;
export type UpdateExampleInput = z.infer<typeof updateExampleSchema>;
export type ExampleParams = z.infer<typeof exampleParamsSchema>;
```
---
### Mongoose Model
**File: `src/modules/example/example.model.ts`**
```typescript
import mongoose, { Document, Schema } from 'mongoose';
export interface IExample extends Document {
name: string;
description?: string;
isActive: boolean;
createdAt: Date;
updatedAt: Date;
}
const exampleSchema = new Schema<IExample>(
{
name: { type: String, required: true, trim: true },
description: { type: String },
isActive: { type: Boolean, default: true },
},
{
timestamps: true,
versionKey: false,
}
);
export const Example = mongoose.model<IExample>('Example', exampleSchema);
```
---
### Service Layer
**File: `src/modules/example/example.service.ts`**
```typescript
import { Example, IExample } from './example.model.js';
import { CreateExampleInput, UpdateExampleInput } from './example.schema.js';
export async function getAllExamples(): Promise<IExample[]> {
return Example.find({ isActive: true }).lean();
}
export async function getExampleById(id: string): Promise<IExample | null> {
return Example.findById(id).lean();
}
export async function createExample(data: CreateExampleInput): Promise<IExample> {
const example = new Example(data);
return example.save();
}
export async function updateExample(
id: string,
data: UpdateExampleInput
): Promise<IExample | null> {
return Example.findByIdAndUpdate(id, data, { new: true, runValidators: true }).lean();
}
export async function deleteExample(id: string): Promise<IExample | null> {
return Example.findByIdAndDelete(id).lean();
}
```
---
### Controller
**File: `src/modules/example/example.controller.ts`**
```typescript
import { FastifyRequest, FastifyReply } from 'fastify';
import { CreateExampleInput, ExampleParams, UpdateExampleInput } from './example.schema.js';
import * as service from './example.service.js';
export async function getAll(req: FastifyRequest, reply: FastifyReply) {
const data = await service.getAllExamples();
return reply.send({ success: true, data });
}
export async function getOne(
req: FastifyRequest<{ Params: ExampleParams }>,
reply: FastifyReply
) {
const item = await service.getExampleById(req.params.id);
if (!item) return reply.notFound('Example not found');
return reply.send({ success: true, data: item });
}
export async function create(
req: FastifyRequest<{ Body: CreateExampleInput }>,
reply: FastifyReply
) {
const data = await service.createExample(req.body);
return reply.status(201).send({ success: true, data });
}
export async function update(
req: FastifyRequest<{ Params: ExampleParams; Body: UpdateExampleInput }>,
reply: FastifyReply
) {
const data = await service.updateExample(req.params.id, req.body);
if (!data) return reply.notFound('Example not found');
return reply.send({ success: true, data });
}
export async function remove(
req: FastifyRequest<{ Params: ExampleParams }>,
reply: FastifyReply
) {
const data = await service.deleteExample(req.params.id);
if (!data) return reply.notFound('Example not found');
return reply.send({ success: true, message: 'Deleted successfully' });
}
```
---
### Route Registration
**File: `src/modules/example/example.route.ts`**
```typescript
import { FastifyInstance } from 'fastify';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
createExampleSchema,
updateExampleSchema,
exampleParamsSchema,
} from './example.schema.js';
import * as controller from './example.controller.js';
export async function exampleRoutes(app: FastifyInstance) {
app.get('/', { schema: { tags: ['Examples'] } }, controller.getAll);
app.get(
'/:id',
{ schema: { tags: ['Examples'], params: zodToJsonSchema(exampleParamsSchema) } },
controller.getOne
);
app.post(
'/',
{
schema: {
tags: ['Examples'],
body: zodToJsonSchema(createExampleSchema),
},
},
controller.create
);
app.put(
'/:id',
{
schema: {
tags: ['Examples'],
params: zodToJsonSchema(exampleParamsSchema),
body: zodToJsonSchema(updateExampleSchema),
},
},
controller.update
);
app.delete(
'/:id',
{ schema: { tags: ['Examples'], params: zodToJsonSchema(exampleParamsSchema) } },
controller.remove
);
}
```
> **Note**: Install `zod-to-json-schema` with: `npm install zod-to-json-schema`
---
## Step 11 — Utility Helpers
**File: `src/utils/response.ts`**
```typescript
export function successResponse<T>(data: T, message = 'Success') {
return { success: true, message, data };
}
export function errorResponse(message: string, errors?: unknown) {
return { success: false, message, errors };
}
```
---
## Step 12 — ESLint & Prettier Config
**File: `.eslintrc.json`**
```json
{
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"env": { "node": true, "es2022": true },
"rules": {
"@typescript-eslint/no-unused-vars": ["warn"],
"@typescript-eslint/explicit-function-return-type": "off"
}
}
```
**File: `.prettierrc`**
```json
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
```
---
## Step 13 — .gitignore
**File: `.gitignore`**
```
node_modules/
dist/
.env
*.log
.DS_Store
```
---
## Conventions & Patterns
### Adding a New Module
To add a new module (e.g., `user`), create the following files following the same pattern:
```
src/modules/user/
├── user.controller.ts
├── user.route.ts
├── user.schema.ts
├── user.model.ts
└── user.service.ts
```
Then register the route in `src/app.ts`:
```typescript
await app.register(userRoutes, { prefix: '/api/v1/users' });
```
### Validation Pattern
Always validate input using Zod schemas in the schema file. Pass type-safe inferred types to controllers and services. Never bypass Zod for request body or params.
### Error Handling
Use `reply.notFound()`, `reply.badRequest()`, and `reply.internalServerError()` from `@fastify/sensible`. Wrap async service calls in try/catch at the controller level for unexpected errors.
---
## API Endpoints (Example Module)
| Method | Path | Description |
|---|---|---|
| GET | `/health` | Health check |
| GET | `/api/v1/examples` | List all examples |
| GET | `/api/v1/examples/:id` | Get one by ID |
| POST | `/api/v1/examples` | Create new |
| PUT | `/api/v1/examples/:id` | Update by ID |
| DELETE | `/api/v1/examples/:id` | Delete by ID |
---
## Final Checklist for AI Initialization
- [ ] Run `npm install` after generating `package.json`
- [ ] Create `.env` from `.env.example` with real values
- [ ] Ensure MongoDB is running locally or provide Atlas URI
- [ ] Run `npm run dev` to verify the server starts
- [ ] Hit `GET /health` to confirm the app is live
- [ ] Check MongoDB connection log for `✅ MongoDB connected`Explore Related AI Rules
Share Your Fastify AI Rules
Have rules that improved your Fastify workflow? Submit them to AI Rules Hub and help the community get better results from AI coding assistants.
