Fastify
You should build your application by registering routes on a FastifyInstance returned from a buildApp() factory. The seed exposes a single GET / route that returns "Hello, World!" — extend it by adding more routes, plugins, and decorators.
Entry file
The solution files are organized under src/:
src/main.ts— the bootstrap script that callsbuildApp()and starts listening on port3000(preloaded, hidden, read-only).src/app.ts— thebuildApp()factory where you register routes and plugins (editable solution file).
Splitting the factory from the listener lets you reuse buildApp() in tests with app.inject() instead of binding to a real port.
Server configuration for API Tester
The server is started by src/main.ts (buildApp().listen({ port: 3000, host: '0.0.0.0' })) so it is running when you use the API Tester tab. The API Tester sends real HTTP requests against your Fastify app and renders the response.
import { buildApp } from './app';
const app = buildApp();
app.listen({ port: 3000, host: '0.0.0.0' }).catch((error) => {
console.error('Server failed to start:', error);
process.exit(1);
});Version
Running on Node.js v22.0.0 with Fastify 5.
Supported languages
TypeScript
Testing framework
Special reminders and implementation details
- Route handlers should be
asyncwhenever they do I/O — Fastify will await the returned promise and serialize the resolved value as the response body. - Don't both call
reply.send()AND return a value — Fastify will warn about double-reply. Pick one style per handler. - Tests use Fastify's built-in
app.inject()HTTP injector so you don't have to actually bind to a port. Alwaysawait app.close()inafterEachso ports don't leak across spec files.
Example with Vitest + app.inject():
import { describe, it, expect, afterEach } from 'vitest';
import type { FastifyInstance } from 'fastify';
import { buildApp } from './src/app';
describe('GET /', () => {
let app: FastifyInstance;
afterEach(async () => {
await app?.close();
});
it("returns 'Hello, World!' with status 200", async () => {
app = buildApp();
const response = await app.inject({ method: 'GET', url: '/' });
expect(response.statusCode).toBe(200);
expect(response.body).toBe('Hello, World!');
});
});Included libraries
How to debug
Fastify is a backend framework — your code runs in Node, not the browser. Three main ways to inspect runtime behavior:
1. Logs from route handlers
Use request.log or console.log. To use the request logger, enable the logger when building the app:
const app = Fastify({ logger: true });
app.get('/items', async (request) => {
request.log.info('GET /items called');
return [];
});Output appears in the dev server terminal (or in the embedded Preview's debug panel).
2. API Tester
Use the API Tester tab in the right panel to send real HTTP requests (GET / POST / PUT / PATCH / DELETE) to your running Fastify server. Pick a method, type a path (e.g. /items), add headers/params/body, and hit Send.
3. Steps to Debug Using Console Logs
Identify the Problem Area: Decide whether the issue is in a route handler, a plugin, or the
buildApp()wiring.Insert
console.log()Statements: Add log calls before and after suspicious code. All Fastify logs go to the server terminal.Check the Results Output of your tests
Use Debug Icon in Preview Area: Click on the debug icon in the Preview area to check the terminal output.
Analyze the Output: Compare logs against the API Tester responses to spot wrong values, missed routes, or unhandled errors.
Common pitfalls
- Forgetting
asyncon a handler that does I/O → return values get serialized before the promise resolves. Always declare async handlers asasync (request, reply) => { ... }. - Calling
reply.send()AND returning a value → Fastify will warn about double-reply. Pick one style per handler. - Not closing the app in tests → port leaks accumulate across spec files. Always
await app.close()inafterEach/afterAll.