Prerequisites
- Basic understanding of JavaScript 📝
- TypeScript installation ⚡
- VS Code or preferred IDE 💻
What you'll learn
- Understand Fastify fundamentals 🎯
- Apply Fastify in real projects 🏗️
- Debug common issues 🐛
- Write type-safe code ✨
🎯 Introduction
Welcome to the world of high-performance web servers! 🎉 In this tutorial, we’ll explore Fastify - one of the fastest Node.js web frameworks available today. When combined with TypeScript, Fastify becomes a powerhouse for building lightning-fast, type-safe APIs! ⚡
You’ll discover how Fastify can transform your backend development experience. Whether you’re building REST APIs 🌐, microservices 🏗️, or full-featured web applications 📱, understanding Fastify is essential for creating performant, maintainable server-side code.
By the end of this tutorial, you’ll feel confident building blazing-fast servers with Fastify and TypeScript! Let’s dive in! 🏊♂️
📚 Understanding Fastify
🤔 What is Fastify?
Fastify is like a Formula 1 race car for web servers 🏎️. Think of it as Express.js’s lightning-fast cousin that’s built from the ground up with performance and developer experience in mind.
In TypeScript terms, Fastify provides excellent type safety out of the box 🛡️. This means you can:
- ✨ Catch routing errors at compile-time
- 🚀 Get incredible performance (up to 65k requests/second!)
- 🛡️ Enjoy built-in validation and serialization
- 📖 Have automatic API documentation generation
💡 Why Use Fastify?
Here’s why developers love Fastify:
- Lightning Speed ⚡: Benchmarks show it’s one of the fastest Node.js frameworks
- TypeScript First 💻: Built with TypeScript developers in mind
- Plugin Architecture 🔌: Modular design that scales beautifully
- Schema Validation 🛡️: Built-in JSON schema validation
- Developer Experience 🎯: Excellent debugging and logging capabilities
Real-world example: Imagine building an e-commerce API 🛒. With Fastify, you can handle thousands of concurrent orders while maintaining type safety and automatic request validation!
🔧 Basic Syntax and Usage
📝 Installation and Setup
Let’s start with setting up our TypeScript Fastify project:
# 🚀 Create a new project
mkdir fastify-typescript-app
cd fastify-typescript-app
# 📦 Initialize package.json
npm init -y
# ⚡ Install Fastify and TypeScript
npm install fastify
npm install -D typescript @types/node ts-node
# 🛠️ Create tsconfig.json
npx tsc --init
🏗️ Basic Server Setup
Here’s your first Fastify TypeScript server:
// 👋 Welcome to Fastify with TypeScript!
import Fastify from 'fastify';
// 🎯 Create our server instance
const server = Fastify({
logger: true // 📝 Enable logging for debugging
});
// 🚀 Define our first route
server.get('/', async (request, reply) => {
return { message: 'Hello, TypeScript Fastify! 🎉' };
});
// ⚡ Start the server
const start = async (): Promise<void> => {
try {
await server.listen({ port: 3000 });
console.log('🚀 Server running on http://localhost:3000');
} catch (err) {
server.log.error(err);
process.exit(1);
}
};
start();
💡 Explanation: Notice how clean this is! Fastify handles JSON serialization automatically, and TypeScript gives us full type safety.
🎯 Route Definitions with Types
Here are typed route patterns you’ll use daily:
// 🏗️ Import FastifyRequest and FastifyReply for typing
import { FastifyRequest, FastifyReply } from 'fastify';
// 🎨 Define request/response types
interface UserParams {
id: string;
}
interface UserBody {
name: string;
email: string;
age: number;
}
// 🔄 GET route with typed parameters
server.get<{ Params: UserParams }>(
'/users/:id',
async (request: FastifyRequest<{ Params: UserParams }>, reply: FastifyReply) => {
const { id } = request.params;
return { userId: id, message: `Getting user ${id} 👤` };
}
);
// ➕ POST route with typed body
server.post<{ Body: UserBody }>(
'/users',
async (request: FastifyRequest<{ Body: UserBody }>, reply: FastifyReply) => {
const { name, email, age } = request.body;
return {
success: true,
user: { name, email, age },
message: 'User created! 🎉'
};
}
);
💡 Practical Examples
🛒 Example 1: E-commerce Product API
Let’s build a real product management system:
// 🛍️ Product types
interface Product {
id: string;
name: string;
price: number;
category: string;
inStock: boolean;
emoji: string; // Every product needs personality!
}
interface CreateProductBody {
name: string;
price: number;
category: string;
emoji: string;
}
interface ProductParams {
id: string;
}
// 📦 In-memory product store (use a real database in production!)
const products: Product[] = [
{ id: '1', name: 'TypeScript Masterclass', price: 99.99, category: 'education', inStock: true, emoji: '📘' },
{ id: '2', name: 'Premium Coffee', price: 12.99, category: 'beverages', inStock: true, emoji: '☕' }
];
// 📋 GET all products
server.get('/products', async (request, reply) => {
return {
products,
total: products.length,
message: 'Products loaded successfully! 🛒'
};
});
// 🎯 GET single product
server.get<{ Params: ProductParams }>(
'/products/:id',
async (request, reply) => {
const { id } = request.params;
const product = products.find(p => p.id === id);
if (!product) {
reply.status(404);
return { error: 'Product not found 😢' };
}
return {
product,
message: `Found ${product.emoji} ${product.name}! 🎉`
};
}
);
// ➕ CREATE new product
server.post<{ Body: CreateProductBody }>(
'/products',
async (request, reply) => {
const { name, price, category, emoji } = request.body;
const newProduct: Product = {
id: Date.now().toString(),
name,
price,
category,
inStock: true,
emoji
};
products.push(newProduct);
reply.status(201);
return {
product: newProduct,
message: `Created ${emoji} ${name}! 🎊`
};
}
);
// 🗑️ DELETE product
server.delete<{ Params: ProductParams }>(
'/products/:id',
async (request, reply) => {
const { id } = request.params;
const index = products.findIndex(p => p.id === id);
if (index === -1) {
reply.status(404);
return { error: 'Product not found 😢' };
}
const deletedProduct = products.splice(index, 1)[0];
return {
message: `Deleted ${deletedProduct.emoji} ${deletedProduct.name} 🗑️`
};
}
);
🎯 Try it yourself: Add an update endpoint and inventory management features!
🎮 Example 2: Game Leaderboard API
Let’s create a fun gaming leaderboard:
// 🏆 Game score types
interface GameScore {
id: string;
playerName: string;
score: number;
level: number;
achievements: string[];
timestamp: Date;
}
interface SubmitScoreBody {
playerName: string;
score: number;
level: number;
}
interface LeaderboardQuery {
limit?: number;
category?: 'top' | 'recent';
}
// 🎮 In-memory leaderboard
const leaderboard: GameScore[] = [
{
id: '1',
playerName: 'TypeScript Master',
score: 15000,
level: 25,
achievements: ['🚀 Speed Demon', '🎯 Perfect Accuracy'],
timestamp: new Date()
}
];
// 📊 GET leaderboard with query parameters
server.get<{ Querystring: LeaderboardQuery }>(
'/leaderboard',
async (request, reply) => {
const { limit = 10, category = 'top' } = request.query;
let sortedScores = [...leaderboard];
if (category === 'top') {
sortedScores.sort((a, b) => b.score - a.score);
} else {
sortedScores.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
}
const topScores = sortedScores.slice(0, limit);
return {
leaderboard: topScores,
category,
total: leaderboard.length,
message: `🏆 Top ${category} scores loaded!`
};
}
);
// 🎯 POST new score
server.post<{ Body: SubmitScoreBody }>(
'/leaderboard',
async (request, reply) => {
const { playerName, score, level } = request.body;
// 🎊 Generate achievements based on score
const achievements: string[] = [];
if (score > 10000) achievements.push('🌟 High Scorer');
if (level > 20) achievements.push('🚀 Level Master');
if (score > 20000) achievements.push('🏆 Champion');
const newScore: GameScore = {
id: Date.now().toString(),
playerName,
score,
level,
achievements,
timestamp: new Date()
};
leaderboard.push(newScore);
// 📈 Check if it's a new high score
const isHighScore = leaderboard.every(s => s.id === newScore.id || s.score < score);
reply.status(201);
return {
score: newScore,
isHighScore,
message: isHighScore ?
`🎉 NEW HIGH SCORE! ${playerName} scored ${score}!` :
`✨ Great job ${playerName}! Score: ${score}`
};
}
);
🚀 Advanced Concepts
🧙♂️ Schema Validation and Serialization
Fastify’s superpower is built-in JSON schema validation:
// 🎯 Define JSON schemas for validation
const createUserSchema = {
body: {
type: 'object',
required: ['name', 'email', 'age'],
properties: {
name: { type: 'string', minLength: 2 },
email: { type: 'string', format: 'email' },
age: { type: 'number', minimum: 0, maximum: 150 }
}
},
response: {
201: {
type: 'object',
properties: {
success: { type: 'boolean' },
user: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' }
}
},
message: { type: 'string' }
}
}
}
};
// 🪄 Apply schema to route
server.post('/users', {
schema: createUserSchema,
handler: async (request, reply) => {
const { name, email, age } = request.body as CreateUserBody;
const user = {
id: Date.now().toString(),
name,
email,
age
};
reply.status(201);
return {
success: true,
user,
message: 'User created with validation! ✨'
};
}
});
🏗️ Plugin Architecture
Create reusable plugins for modular code:
// 🔌 Custom authentication plugin
import fp from 'fastify-plugin';
interface AuthOptions {
secret: string;
}
async function authPlugin(fastify: any, options: AuthOptions) {
// 🛡️ Add authentication decorator
fastify.decorate('authenticate', async (request: any, reply: any) => {
const token = request.headers.authorization;
if (!token) {
reply.status(401);
throw new Error('🚫 No token provided');
}
// 🔍 Simple token validation (use JWT in production!)
if (token !== `Bearer ${options.secret}`) {
reply.status(401);
throw new Error('🚫 Invalid token');
}
// ✅ Token is valid
request.user = { id: '1', name: 'Authenticated User' };
});
}
// 📦 Export plugin
export default fp(authPlugin, {
name: 'auth-plugin'
});
// 🎯 Register and use the plugin
server.register(authPlugin, { secret: 'super-secret-key' });
// 🔒 Protected route
server.get('/protected', {
preHandler: server.authenticate
}, async (request, reply) => {
return {
message: 'Welcome to the protected zone! 🛡️',
user: request.user
};
});
⚠️ Common Pitfalls and Solutions
😱 Pitfall 1: Forgetting Async/Await
// ❌ Wrong - forgetting async/await
server.get('/users', (request, reply) => {
getUsersFromDatabase(); // 💥 Returns a Promise but not awaited!
return { users: [] };
});
// ✅ Correct - proper async handling
server.get('/users', async (request, reply) => {
const users = await getUsersFromDatabase(); // ✨ Properly awaited!
return { users };
});
🤯 Pitfall 2: Not Handling Errors Properly
// ❌ Dangerous - no error handling
server.get('/risky', async (request, reply) => {
const data = await riskyDatabaseCall(); // 💥 Might throw!
return data;
});
// ✅ Safe - proper error handling
server.get('/safe', async (request, reply) => {
try {
const data = await riskyDatabaseCall();
return { success: true, data };
} catch (error) {
server.log.error(error);
reply.status(500);
return { error: 'Something went wrong! 😅' };
}
});
🚫 Pitfall 3: Incorrect TypeScript Types
// ❌ Wrong - using any defeats the purpose
server.get('/bad-types', async (request: any, reply: any) => {
const data = request.body; // 💥 No type safety!
return data;
});
// ✅ Correct - proper typing
interface RequestBody {
name: string;
age: number;
}
server.post<{ Body: RequestBody }>('/good-types',
async (request, reply) => {
const { name, age } = request.body; // ✨ Fully typed!
return { greeting: `Hello ${name}, age ${age}!` };
}
);
🛠️ Best Practices
- 🎯 Use TypeScript Generics: Always type your request/response objects
- 📝 Leverage Schema Validation: Let Fastify validate requests automatically
- 🛡️ Handle Errors Gracefully: Use try-catch and proper error responses
- 🔌 Create Plugins: Make your code modular and reusable
- ⚡ Enable Logging: Use Fastify’s built-in logger for debugging
- 🏗️ Structure Your Code: Separate routes, schemas, and business logic
- ✨ Use Reply Status Codes: Be explicit about HTTP status codes
🧪 Hands-On Exercise
🎯 Challenge: Build a Task Management API
Create a complete task management system with Fastify and TypeScript:
📋 Requirements:
- ✅ CRUD operations for tasks (Create, Read, Update, Delete)
- 🏷️ Task categories (work, personal, urgent)
- 👤 User assignment and authentication
- 📅 Due dates and priority levels
- 🎨 Each task needs an emoji and validation!
- 🔍 Search and filter functionality
🚀 Bonus Points:
- Add task completion statistics
- Implement task assignment to users
- Create a daily task summary endpoint
- Add task reminder functionality
💡 Solution
🔍 Click to see solution
// 🎯 Our complete task management API!
import Fastify from 'fastify';
import { FastifyRequest, FastifyReply } from 'fastify';
const server = Fastify({ logger: true });
// 📝 Task interface
interface Task {
id: string;
title: string;
description: string;
completed: boolean;
priority: 'low' | 'medium' | 'high';
category: 'work' | 'personal' | 'urgent';
emoji: string;
dueDate?: Date;
assignee?: string;
createdAt: Date;
}
// 🏗️ Request types
interface CreateTaskBody {
title: string;
description: string;
priority: 'low' | 'medium' | 'high';
category: 'work' | 'personal' | 'urgent';
emoji: string;
dueDate?: string;
assignee?: string;
}
interface UpdateTaskBody extends Partial<CreateTaskBody> {
completed?: boolean;
}
interface TaskParams {
id: string;
}
interface TaskQuery {
category?: string;
completed?: boolean;
assignee?: string;
limit?: number;
}
// 📊 In-memory task store
const tasks: Task[] = [
{
id: '1',
title: 'Learn Fastify',
description: 'Complete the TypeScript Fastify tutorial',
completed: false,
priority: 'high',
category: 'work',
emoji: '🚀',
dueDate: new Date('2024-12-31'),
assignee: 'developer',
createdAt: new Date()
}
];
// 📋 GET all tasks with filtering
server.get<{ Querystring: TaskQuery }>('/tasks', async (request, reply) => {
const { category, completed, assignee, limit = 50 } = request.query;
let filteredTasks = [...tasks];
if (category) {
filteredTasks = filteredTasks.filter(t => t.category === category);
}
if (completed !== undefined) {
filteredTasks = filteredTasks.filter(t => t.completed === completed);
}
if (assignee) {
filteredTasks = filteredTasks.filter(t => t.assignee === assignee);
}
const paginatedTasks = filteredTasks.slice(0, limit);
return {
tasks: paginatedTasks,
total: filteredTasks.length,
filters: { category, completed, assignee },
message: `📋 Found ${paginatedTasks.length} tasks!`
};
});
// 🎯 GET single task
server.get<{ Params: TaskParams }>('/tasks/:id', async (request, reply) => {
const { id } = request.params;
const task = tasks.find(t => t.id === id);
if (!task) {
reply.status(404);
return { error: 'Task not found 😢' };
}
return {
task,
message: `Found task: ${task.emoji} ${task.title}! 🎉`
};
});
// ➕ CREATE new task
server.post<{ Body: CreateTaskBody }>('/tasks', async (request, reply) => {
const { title, description, priority, category, emoji, dueDate, assignee } = request.body;
const newTask: Task = {
id: Date.now().toString(),
title,
description,
completed: false,
priority,
category,
emoji,
dueDate: dueDate ? new Date(dueDate) : undefined,
assignee,
createdAt: new Date()
};
tasks.push(newTask);
reply.status(201);
return {
task: newTask,
message: `Created task: ${emoji} ${title}! 🎊`
};
});
// ✏️ UPDATE task
server.put<{ Params: TaskParams; Body: UpdateTaskBody }>('/tasks/:id', async (request, reply) => {
const { id } = request.params;
const updates = request.body;
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
reply.status(404);
return { error: 'Task not found 😢' };
}
const updatedTask = {
...tasks[taskIndex],
...updates,
dueDate: updates.dueDate ? new Date(updates.dueDate) : tasks[taskIndex].dueDate
};
tasks[taskIndex] = updatedTask;
return {
task: updatedTask,
message: `Updated task: ${updatedTask.emoji} ${updatedTask.title}! ✨`
};
});
// 🗑️ DELETE task
server.delete<{ Params: TaskParams }>('/tasks/:id', async (request, reply) => {
const { id } = request.params;
const taskIndex = tasks.findIndex(t => t.id === id);
if (taskIndex === -1) {
reply.status(404);
return { error: 'Task not found 😢' };
}
const deletedTask = tasks.splice(taskIndex, 1)[0];
return {
message: `Deleted task: ${deletedTask.emoji} ${deletedTask.title} 🗑️`
};
});
// 📊 GET task statistics
server.get('/tasks/stats', async (request, reply) => {
const total = tasks.length;
const completed = tasks.filter(t => t.completed).length;
const pending = total - completed;
const byCategory = {
work: tasks.filter(t => t.category === 'work').length,
personal: tasks.filter(t => t.category === 'personal').length,
urgent: tasks.filter(t => t.category === 'urgent').length
};
const byPriority = {
high: tasks.filter(t => t.priority === 'high').length,
medium: tasks.filter(t => t.priority === 'medium').length,
low: tasks.filter(t => t.priority === 'low').length
};
return {
stats: {
total,
completed,
pending,
completionRate: total > 0 ? Math.round((completed / total) * 100) : 0,
byCategory,
byPriority
},
message: '📊 Task statistics loaded!'
};
});
// 🚀 Start the server
const start = async () => {
try {
await server.listen({ port: 3000 });
console.log('🎉 Task Management API running on http://localhost:3000');
console.log('📋 Try these endpoints:');
console.log(' GET /tasks - List all tasks');
console.log(' POST /tasks - Create a new task');
console.log(' GET /tasks/stats - View statistics');
} catch (err) {
server.log.error(err);
process.exit(1);
}
};
start();
🎓 Key Takeaways
You’ve learned so much about Fastify with TypeScript! Here’s what you can now do:
- ✅ Build high-performance APIs with confidence 💪
- ✅ Implement type-safe routing that catches errors early 🛡️
- ✅ Create modular plugins for reusable functionality 🎯
- ✅ Handle validation and serialization automatically 🐛
- ✅ Structure production-ready applications with best practices! 🚀
Remember: Fastify + TypeScript = Performance + Safety! It’s a winning combination for modern web development. 🤝
🤝 Next Steps
Congratulations! 🎉 You’ve mastered Fastify with TypeScript!
Here’s what to do next:
- 💻 Practice with the task management API above
- 🏗️ Build a real project using Fastify (maybe a social media API? 📱)
- 📚 Explore Fastify plugins ecosystem (database connectors, authentication, etc.)
- 🌟 Try deploying your Fastify app to production
- 🔄 Learn about testing Fastify applications
Next up in our backend journey: Express.js with TypeScript: Traditional Web Framework - where we’ll compare and contrast with Fastify! 🎯
Remember: Every backend expert was once a beginner. Keep building, keep learning, and most importantly, have fun with your blazing-fast APIs! 🚀
Happy coding! 🎉🚀✨