TDD a Modern TypeScript Rest API with Fastify
Initialize Your Project
Start by creating a new directory for your Fastify project.
```bash
mkdir fastify-ts-app
cd fastify-ts-app
Then, initialize your project with npm
:
npm init -y
This will create a package.json
file in your project directory.
Add "type": "module"
to the package.json
file.
{
// ...other properties
"type": "module"
// ...other properties
}
Install the necessary dependencies for Fastify.
npm install fastify
Then, install TypeScript and the required type definitions as development dependencies.
npm install -D typescript @types/node tsx fastify-tsconfig pino-pretty
Run the following command to initialize a TypeScript configuration file:
npx tsc --init
This will create a tsconfig.json
file. Update it to set the output directory for compiled files:
{
"extends": "fastify-tsconfig",
"compilerOptions": {
"allowJs": true,
"moduleDetection": "force",
"noImplicitOverride": true,
"noUncheckedIndexedAccess": true,
"outDir": "dist",
"paths": {
"~/*": [
"./src/*"
]
},
"sourceMap": true,
"verbatimModuleSyntax": true
},
"exclude": [
"node_modules",
"dist"
],
"include": [
"src/**/*"
]
}
Create a src
directory for your TypeScript files:
mkdir src
Inside the src
directory, create an index.ts
file with the following content:
import fastify from 'fastify';
const app = fastify();
const port = process.env.PORT || 3000;
app.get('/', async () => {
return 'Fastify + TypeScript Server';
});
const start = async () => {
try {
await app.listen({ port: Number(port), host: '0.0.0.0' });
console.log(`Server is running at http://localhost:${port}`);
} catch (error) {
app.log.error(error);
process.exit(1);
}
};
start();
Update your package.json
to include build and run scripts:
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "fastify start -w src/index.ts",
}
build
: Compiles TypeScript files to JavaScript.start
: Runs the compiled JavaScript file.dev
: Runs the TypeScript file directly with live reload.
For development, use the following command to start the server with live reload:
npm run dev
Visit http://localhost:3000
to verify your setup.
Later, if you want to prepare for production, first build your project:
npm run build
Then start the compiled application:
npm start
Add ESLint and Prettier
npm install --save-dev eslint eslint-config-prettier eslint-plugin-prettier eslint-plugin-simple-import-sort eslint-plugin-unicorn prettier typescript @typescript-eslint/eslint-plugin @vitest/eslint-plugin
prettier.config.js
export default {
arrowParens: 'avoid',
bracketSameLine: false,
bracketSpacing: true,
htmlWhitespaceSensitivity: 'css',
insertPragma: false,
jsxSingleQuote: false,
plugins: [],
printWidth: 80,
proseWrap: 'always',
quoteProps: 'as-needed',
requirePragma: false,
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'all',
useTabs: false,
};
eslint.config.js
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
import simpleImportSort from 'eslint-plugin-simple-import-sort';
import vitest from '@vitest/eslint-plugin';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
eslintPluginUnicorn.configs['flat/recommended'],
{
files: ['**/*.{js,ts}'],
ignores: ['**/*.js', 'dist/**/*', 'node_modules/**/*'],
plugins: {
'simple-import-sort': simpleImportSort,
},
rules: {
'simple-import-sort/imports': 'error',
'simple-import-sort/exports': 'error',
'unicorn/better-regex': 'warn',
'unicorn/no-process-exit': 'off',
},
},
{
files: ['**/*.test.{js,ts}'],
...vitest.configs.recommended,
},
eslintPluginPrettierRecommended,
);
Add Vitest
npm install -D vitest vite-tsconfig-paths
import { defineConfig } from 'vite';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [tsconfigPaths()],
test: { environment: 'node' },
});
Split into Server and app
Logging
Fastify comes with built-in logging support. You can customize the logger or integrate with other logging solutions as needed.
const app = Fastify({
logger: true, // Enables built-in logging
});
- Error Handling: Fastify provides robust error handling mechanisms. Ensure you handle errors gracefully in your routes and consider setting up global error handlers.
In API design, a route defines the path and HTTP method (e.g., GET, POST) that a client uses to access a specific resource or functionality. An endpoint refers to the specific URL where this resource or functionality is accessible. The controller contains the logic that executes when a route is accessed. In summary, routes and endpoints specify how and where clients can access resources, while controllers define what happens when those routes are accessed.
Routes and endpoints are often used interchangeably in casual discussions, but technically:
- Route: Emphasizes the combination of HTTP method and URL path.
- Endpoint: Focuses on the specific URL (which may implicitly include the method when considering the full API operation).
- Controller: A container for related methods/actions/handlers. Universally refers to the logic handling the requests directed by routes / endpoints.
- Method/Action: A specific function within the controller that handles a particular request.
Consider the following HTTP request:
-
Request: GET https://api.example.com/users/123 Breakdown:
-
Endpoint: https://api.example.com/users/123
-
Route: GET
/users/:id
-
Controller Action:
getUserById
function (action / method / handler) in theuserController
.