SuperAPI AI Directory

backend-fastify

Coding & development

View repository

Updated 10-15 22:40

About this skill

Backend Fastify Stack (Bun)

High-performance backend stack, run on Bun:

  • Bun - Runtime, package manager, test runner, and watch-mode dev server (no Node.js, npm, or tsx)
  • Fastify 5.x - Web framework
  • TypeScript - Type safety, executed directly by Bun (no build step for services)
  • pino-pretty - Pretty logging for development
  • @fastify/env - Environment variable validation with JSON Schema
  • @fastify/cors - CORS support
  • fastify-plugin - Plugin system

Bun runs TypeScript source directly, so there is no compile step in the dev loop and none in production for a service — you ship and run the source. tsc is kept only as a type checker (tsc --noEmit), never as a build tool.

Environment Setup

Use asdf to manage Bun:

# Install the Bun plugin (one-time)
asdf plugin add bun

# Pin Bun for the project (writes .tool-versions)
asdf set bun latest
asdf install

This writes a .tool-versions file pinning Bun so the whole team runs the same version. A born-on-Bun service needs only bun in .tool-versions — add nodejs only if the repo also ships an npm-distributed CLI that needs the Node toolchain.

Reference Files

File When to Use
setup-guide.md Starting a new backend project from scratch
patterns.md Implementing routes, services, schema validation
authentication.md Adding JWT auth, authorization, protected routes
api-design.md Swagger/OpenAPI integration, response format, errors
mcp-integration.md Integrating MCP server for AI agent access
gotchas.md Debugging issues, common mistakes and fixes

Quick Reference

Commands

# Dev server with watch mode (Bun reloads on change)
bun run dev          # → bun --watch src/index.ts

# Type check (no emit — Bun runs the source, tsc only checks types)
bun run check        # → tsc --noEmit

# Run the server (no build needed — Bun executes the source)
bun run start        # → bun run src/index.ts

# Tests (Bun's built-in runner)
bun test

Package Management

Use Bun for all dependency management — never edit package.json by hand:

bun add fastify-plugin @fastify/cors        # runtime deps
bun add -d @types/bun pino-pretty           # dev deps
bun remove <pkg>                            # remove

bun add resolves the latest compatible version and keeps bun.lock in sync. Commit bun.lock.

Key Imports

// Fastify types
import Fastify, { FastifyInstance, FastifyPluginAsync } from 'fastify'
import fp from 'fastify-plugin'

// Common plugins
import fastifyEnv from '@fastify/env'
import cors from '@fastify/cors'

Configuration Access

Always access configuration through fastify.config, never process.env directly:

// CORRECT - type-safe, validated at startup
const port = fastify.config.PORT
const apiKey = fastify.config.API_KEY

// WRONG - no validation, no type safety
const port = process.env.PORT  // Don't do this

Response Format

// Standard response structure
{
  success: boolean
  data?: T
  error?: string
  metadata?: { timestamp: Date }
}

Plugin Pattern

import fp from 'fastify-plugin'

export default fp(async (fastify, opts) => {
  // Plugin logic here
  fastify.decorate('something', value)
}, '5.x')

Route Pattern

import { FastifyPluginAsync } from 'fastify'

const routes: FastifyPluginAsync = async (fastify, opts) => {
  fastify.get('/', async (request, reply) => {
    return { success: true, data: 'example' }
  })
}

export default routes

Service Pattern

// 1. Create service class
export class MyService {
  constructor(private fastify: FastifyInstance) {}

  async doWork() {
    // Access config through fastify instance
    const apiKey = this.fastify.config.API_KEY
    this.fastify.log.info('Service method called')
  }
}

// 2. Declare module augmentation
declare module 'fastify' {
  interface FastifyInstance {
    myService: MyService
  }
}

// 3. Initialize and decorate in app.ts
fastify.decorate('myService', new MyService(fastify))

TypeScript Config

Bun runs the source directly, so tsconfig.json is for type checking only ("noEmit": true). The Bun-native config:

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "Preserve",          // let Bun own module resolution
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "types": ["@types/bun"],        // Bun globals (Bun.file, Bun.serve, etc.)
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "skipLibCheck": true,
    "noEmit": true
  }
}

In a Bun-workspaces monorepo, the package tsconfigs use TypeScript project references ("composite": true, per-package references) and the root runs tsc -b; the runtime story is unchanged — Bun still executes the source.

Project Structure

backend/
├── src/
│   ├── plugins/          # Fastify plugins (env, auth, etc.)
│   ├── routes/           # HTTP route handlers
│   ├── services/         # Business logic classes
│   ├── lib/              # Shared utilities / clients
│   ├── app.ts            # Plugin registration & setup
│   └── index.ts          # Server entry point
├── package.json
├── tsconfig.json
├── .env.example
└── .gitignore

Common Gotchas

  • Plugin order matters: Register env plugin first, then services, then routes
  • Config access: Use fastify.config.VAR not process.env.VAR
  • Server ready: Call await server.ready() before accessing config in index.ts
  • No build step: Bun runs the source; tsc is --noEmit for type checking only
  • Path normalization: Centralize path utilities, handle root '/' as special case
  • Package management: Use bun add <pkg> not manual package.json edits