Published on June 23, 2025
Fastify is a lightweight and flexible Node.js web framework used to build dynamically generated web pages and high-performance API back ends. It focuses on being developer-friendly and helps bootstrap your apps with a high-performance, extensible framework to base your own code on.
This post explains Fastify and its advantages and use cases, and it provides practical tutorial steps to get you started building your own Fastify apps.
Fastify is a web framework for Node.js. It places a priority on performance, having low overheads, and it has built-in security features and long-term support.
Fastify gives you a foundation for building web services, including dynamically generated web pages (web pages that are rendered server-side) and REST APIs (programmatic back ends that you can connect standalone client applications to, including web apps, native mobile apps, automation scripts, and even AI assistants).
Fastify provides common web framework functionality such as routes, error handling, form and JSON data parsing, validation and serialization, and hooks that let you tap into Fastify's request and response lifecycle. You can extend Fastify using plugins that you can either develop yourself or find in the extensive Fastify plugin ecosystem. Plugins allow you to (nearly) effortlessly add extra functionality to Fastify like authentication, environment variables, page view templates, and database connectivity.
You can host Fastify manually on your own servers using Node.js or deploy it using Docker or Kubernetes. It’s also supported in many serverless environments such as AWS Lambda, Google Cloud Functions, and Vercel.
Fastify was inspired by the Express web framework with the aim of fixing some long-term pain points developers had experienced with Express. This included performance issues, a lack of built-in support for JSON schemas for validation and serialization, and async support. Fastify's plugin system is designed to provide better performance and modularity than Express middleware (up until recently, Fastify even supported Express middleware, but it now requires a plugin for this). These differences and additional features can make Fastify a little more complex to use, but may reduce the overall work required to bring a project into production.
Compared to Express, Fastify promotes a more structured approach to development, improving both long-term maintainability and scalability. Both Node.js web frameworks support TypeScript (though Fastify has better native TypeScript support), and you can use both to build APIs and dynamically generated websites. API services built using Fastify and Express can be used to build components in a microservices-based architecture.
Express version 5 onwards can automatically handle async errors, bringing it up to speed with Fastify in this area. Fastify includes hot-reloading and Pino for logging, whereas with Express you need to integrate additional packages for this developer-friendly functionality.
Whether Fastify or Express is the best fit for your project is a matter of developer preference and feature requirements: Express is well suited for simple applications, while Fastify gives better performance and more control with decorators, hooks, and plugins. You should assess which features each Node.js web framework includes; what can be added with reputable, well-supported plugins; and what functionality you will need to implement yourself to decide which is most suitable.
Here's a GitHub repo containing this tutorial's completed code if you want to get right into it.
You can manually install Fastify and create the necessary files, add it to an existing Node.js project, or use create-fastify
to create a new project with everything you need to start building with the Fastify web framework.
To use create-fastify
, you'll need the latest version of Node.js installed. Then run the following command:
npm init fastify my-fastify-app
This will create a new Node.js Fastify app with the following files and directories:
A README.md
file for documenting your project (pre-populated with instructions on how to launch your new Fastify app).
A package.json
file listing your project’s details and dependencies.
app.js
, which is run when you launch your Fastify app
The /routes
directory, which contains a default root.js
file for / (which returns a JSON response) as well as an /example
route (which returns a text/HTML response).
/plugins
for storing plugins, with an example plugin.
A /test
directory for storing code for testing your app logic.
Next, run the following command in the directory created by create-fastify
to install dependencies:
npm install
This tutorial won't cover tests; however, they are vital in complex applications and can greatly reduce the work required to maintain large codebases by automating the testing of new updates.
The example pages generated by create-fastify
show you how to create routes that return simple JSON and text responses. Manually generating the HTML required for more complex web pages (including metadata, navigation, headers, footers, and so on) is laborious.
This is where templates come in: they are modular, reusable chunks of HTML that you can insert your page data into, rather than fully coding each page for each route. You can use the EJS template library for this, and you can enable it in Fastify using the point-of-view plugin. Install both by running:
npm install @fastify/view ejs
To add the @fastify/view
plugin, insert the following code right after the const Autoload = require(‘fastify/autoload’)
line in your app.js
file:
const fastifyView = require("@fastify/view");
In the same file, add the following code under the line that says // Place here your custom code!
to register the plugin:
You'll then need to create a /views
directory for storing your EJS templates. Within it, create a file named display_time.ejs
and add the following code:
Next, you can create a new route for rendering a page generated using an EJS template by creating a directory named get_time
in the existing routes
directory. Then, create a file named index.js
within the get_time
directory and add the following code:
This demonstrates a dynamically generated page that uses calculated data (the current time), and passes its value to a reusable template. This means that you do not need to include the template code in your route file, separating the display aspect into a different file and making the code much easier to read and maintain (following the model-view-controller pattern). You could also reuse the template file for different routes that display similar information, reducing the amount of code overall.
create-fastify
has done a bit of legwork here for you: Node.js modules in the /routes
directory are automatically loaded by app.js
as Fastify routes, with the URL based on the folder path. You can list the routes in a Fastify app using the fastify-print-routes plugin, which can come in useful when developing complex apps and APIs.
You can run the app using the command:
npm run dev
You can access this page at the address http://localhost:3000/get_time
Fastify can process form content from a HTTP request to a route using the @fastify/formbody
plugin. Install it by running:
npm install @fastify/formbody
Add the following line to app.js
(below where you registered fastifyView
described earlier) to register the plugin:
fastify.register(require('@fastify/formbody'));
Next, create the template file for the HTML form in /views/shopping_list.ejs
and add the following code to it:
Then, create /routes/shopping_list/index.js
with the following code:
This route and view template displays the shopping list that is saved to a global variable (meaning it will persist between page loads, but not if the app is restarted), and a form for submitting new shopping list items. Validation and serialization are handled using Fastify's built-in JSON schema support.
You can access this page at the following address: http://localhost:3000/shopping_list
For long-term data storage that will survive your app restarting (or crashing), you need to use a database. While you can manually create a database and write your own queries for saving and loading data to it, an ORM like Prisma can do a lot of the work for you, and allows you to easily swap out which database you're using (in this case, SQLite) with a different one between development and production. Install Prisma by running the following command in your project directory:
npm install prisma @prisma/client
Next, initialize Prisma:npx prisma init
And edit the generated prisma/schema.prisma
file to contain the following code:
This tells Prisma the structure of the database, including the shopping list items and the fields that will need to be stored (a unique id
, name
, price
, and the time the item was added
).
Update the DATABASE_URL
environment variable in the .env
file:
DATABASE_URL="file:./shopping_dev.db"
Note that the URL should be the path to the SQLite database file relative to your apps directory.
Generate the database file, apply the schema to it, and create the Prisma client by running the Prisma migrate
command:
npx prisma migrate dev --name init
You must have created the schema.prisma
file before this, as the database and client will be created using it.
Finally, replace your code in /routes/shopping_list/index.js
with the following code, which uses Prisma to handle loading and saving shopping list items:
The above code uses the Prisma findMany()
and create()
functions to load and save database data.
As this code is using a live database, you should switch to using the npm run start
command instead of npm run dev
to run your app. This is because the dev
script passes a -w
watch flag to the fastify
command, ensuring the server gets restarted when any code files change, and you don't want this to happen for database files since it causes unexpected restarts and errors. You can see the difference between the start
and dev
scripts in your package.json
file.
By default, the above code will return a HTTP 400
status with a JSON error when invalid data is submitted (for example, a price that is less than zero):
Instead, you probably want to tell users what they entered wrong and provide a link back to the form. This can be done with a custom error page.
Create an EJS template for the validation error in the file views/validation_error.ejs
:
You may also want to pass validation errors back to the form view and display the messages alongside their respective form elements, or use live validation. You can also use the include call
feature of EJS to embed templates within other templates.
Register the custom error handler in app.js
by adding the following code below the line that says // Place here your custom code!
:
Then, when a 400 error occurs and validation error is present, the template will be rendered with information from the error, rather than the default JSON response.
If you want to log unexpected errors for later debugging, you can enable logging and take advantage of Fastify's built-in support for Pino.
Fastify isn't just for building server-side rendered apps as shown in the above example. You can also build APIs that can provide data and functionality to mobile, web, desktop, and other applications (including autonomous AI agents!) in an API-first development approach, or even use Fastify in a microservices-based architecture. You're then free to build any kind of frontend application using JavaScript frameworks such as React, Angular, or Svelte, and even create different front ends targeting different devices and use cases.
You can also connect Fastify apps and services to other services in a composable architecture. By leveraging SaaS platforms that provide pre-built ecommerce, content delivery, communication, and other common functionality, you can greatly reduce the work required to implement and maintain the features your unique app requires.
Fastify is ideal for coding the business logic you require to make your apps unique; however, you don't need to use it to build your entire back end. Content management, optimization, and delivery are often monumental tasks that, while vital, take up unnecessary development time and add ongoing infrastructure and maintenance overheads to your apps.
Using the Contentful platform to upload, curate, and deliver all of your images, text, videos, and other content, greatly streamlines the development process. Your content is delivered to your audience faster using our global CDN accessed via REST and GraphQL APIs, meaning less infrastructure overheads and a better end user experience. We even provide SDKs for popular languages and frameworks. Integrating Contentful with your Fastify app is as simple as adding the Contentful JavaScript SDK in either your frontend or backend code (depending on where you wish to load your content):
Then, use the Contentful client to load your content, and standard JavaScript, HTML, and CSS to render it:
You can see the Contentful JavaScript SDK in action, including the rendering of content, in this JSFiddle.
You can customize Contentful for editing rich text content, uploading images, and optimizing them automatically. Contentful even provides live previews of exactly what your published content will look like to your users. Our super fast global content delivery network then makes sure your assets load as fast as possible, wherever they’re needed — from your customers’ mobile devices to highway-spanning billboards.
Subscribe for updates
Build better digital experiences with Contentful updates direct to your inbox.