Fastify is a high-performance web framework for Node.js, designed with speed and low overhead in mind. It offers an intuitive plugin architecture and solid schema validation, making it especially useful for API development, where performance and developer experience are essential.
This guide will walk you through building a full-featured blog API with Fastify and Prisma ORM with SQLite. You'll learn how the Fastify ecosystem approaches routing, data validation, database integration, and error handling.
Prerequisites
To follow this guide, you should have:
- Node.js installed on your system (preferably the latest LTS version, Node.js 22.x or higher)
- A basic understanding of JavaScript, Node.js, and web development concepts
Step 1 — Setting up the Fastify project
Before diving into building your API, it’s essential to start with a solid foundation. A well-structured project keeps your code organized, easy to maintain, and scalable as your application grows.
To begin, create a dedicated project folder and move into it:
Initialize the Node.js project using npm’s default settings to generate a basic package.json file:
Now, configure your project to use ES modules:
Then, install the core dependencies required for your API:
Here's a brief overview of each dependency and its role in your API architecture:
fastify– A high-performance web framework with a focus on efficiency and developer experience.@prisma/client– A type-safe database client that simplifies database interactions through a modern query API.@fastify/cors– A plugin enabling Cross-Origin Resource Sharing (CORS), allowing secure API access from different origins.pino-pretty– A formatter for Fastify's built-in logger that makes development logs more readable.@fastify/env– A plugin for loading and validating environment variables.uuid– A utility to generate universally unique identifiers for database records.
Now, install Prisma as a development dependency to manage your database schema and migrations:
Next, create your main application file, app.js. This file initializes your Fastify server and sets up essential plugins:
This initialization sets up your Fastify application, beginning with importing key dependencies. It then creates a Fastify instance with pretty-printed logging for a better development experience. The CORS plugin is registered to enable cross-origin requests, and a simple root route is defined to verify that the API is functioning correctly.
The start function launches the server using async/await syntax, with proper error handling to exit gracefully if startup fails.
Now, update your project's start script in package.json to use Node.js's built-in watch mode. This allows the server to restart whenever you make changes automatically:
Start the application with:
You'll see output like this:
This indicates your Fastify server is active. To test it, open http://localhost:3000/ in your browser, which should display the welcome message as a JSON response:
At this point, your Fastify API is set up and running. In the next step, you'll integrate a Prisma database with SQLite.
Step 2 — Configuring the database with Prisma
Now that your Fastify server is running, you must set up Prisma with SQLite for database management. This will allow you to define your data model and create database operations in a type-safe way.
First, initialize Prisma in your project:
This command creates:
- A
prismadirectory with aschema.prismafile to define your database schema - A
.envfile in the project root to store environment variables
Upon running the command, you will see output similar to the following:
The output confirms that Prisma has successfully created the schema.prisma file and guides you through configuring your database connection, pulling the schema, and generating the Prisma Client.
Next, update the generated schema.prisma file to use SQLite and define a blog post model:
Now update the generated .env file to set the database URL:
Create a data directory to store your SQLite database file:
Generate the Prisma client:
Then, create your database schema:
The output will look similar to this:
The output confirms that Prisma has successfully loaded the environment variables, set up the SQLite database, synced it with the schema, and generated the Prisma Client.
Next, create a utility module for database access. First, create the directory in your project's root directory:
Then create utils/db.js with the following contents:
Finally, update your app.js file to import and initialize the database connection:
Once you've completed these steps, save the changes, and your server will auto-restart. You should see a message confirming the database connection was established successfully.
This setup creates a foundation for your API, with Prisma managing the SQLite database. In the next step, you'll define validation schemas and implement CRUD operations for blog posts.
Step 3 — Setting up schema validation with Fastify
A key feature of Fastify is its built-in schema validation, which uses JSON Schema to validate requests and responses without external libraries. This improves data integrity and keeps performance high.
Let's create a directory to organize your validation schemas:
Now, create a file called schemas/post.js. First, let's define a base schema that represents the structure of a blog post:
This postSchema defines what a post object looks like in your API. It specifies that a post has an ID (which should be a UUID), a title and content (both strings), a published flag (boolean), and creation and update timestamps.
Next, add a schema for creating new posts. This will validate POST requests to your API:
The createPostSchema validates request bodies for post creation. It requires a title (between 3-100 characters) and content (at least 10 characters), with an optional published flag. The additionalProperties: false ensures no unexpected fields are included. The response schema specifies that a successful creation (status 201) should return a post object.
Now, add a schema to validate GET requests for retrieving multiple posts:
This schema validates query parameters (allowing filtering by published status) and ensures the response is an array of post objects.
Next, add a schema for retrieving a single post by its ID:
The getPostSchema validates the ID route parameter, ensuring it's a valid UUID. It also defines response formats for both success (returning a post) and not found errors.
For updating posts, add a schema that validates both the ID parameter and the request body:
Finally, add a schema for deleting posts:
The deletePostSchema validates the ID parameter and defines response formats for successful deletion (204 No Content) and not found errors.
Finally, don't forget to export all schemas so they can be imported into your route files:
When these schemas are attached to routes, Fastify automatically validates incoming requests before they reach your handlers. If validation fails, Fastify rejects the request with a detailed error message. For example, if someone tries to create a post with a title that's too short, they'll receive an error response.
With your validation schemas defined, you're ready to implement the API routes in the next step.
Step 4 — Creating blog posts
With your database configured and validation schemas in place, it's time to create the API endpoints that will power your blog application. In Fastify, routes are typically organized using plugins, making your code modular and maintainable.
First, create a routes directory in your project root:
Next, create a file called routes/posts.js to implement the blog post endpoints. This file will export a Fastify plugin that registers all your post-related routes:
This implementation creates a Fastify plugin that registers a single POST route for now. The route handler uses the Prisma client to insert a new post record into the database and returns the created post with a 201 status code.
The schema option applies the createPostSchema you defined earlier, which validates the request body before the handler runs. This ensures that only valid data reaches your database operations.
Now update your app.js file to register this routes plugin:
The fastify.register(postRoutes, { prefix: '/api/posts' }) line registers your posts routes plugin with a prefix of /api/posts. This means the POST route you defined will be accessible at /api/posts.
Save your changes and test the endpoint by creating a new blog post using curl:
You should receive a response like this:
Test the validation by trying to create a post with a title that's too short:
You should receive a validation error:
This confirms that Fastify's schema validation is working correctly. The validation runs before your route handler, so invalid requests never reach your database code.
Now, you've implemented the first endpoint of your blog API, allowing users to create new posts. In the next step, you'll add the GET endpoints to retrieve posts from the database.
Step 5 — Retrieving blog posts
Now that you can create blog posts, let's implement functionality to retrieve them. We'll start with an endpoint to fetch all posts, then add another endpoint to get a specific post by its ID.
First, let's update your routes/posts.js file to add a GET method for retrieving all posts:
This endpoint retrieves multiple posts with optional filtering. It uses Prisma's findMany method to query the database and supports a query parameter to filter by published status. The posts are sorted by creation date, with the newest first.
The schema option applies the getAllPostsSchema you defined earlier, which validates the query parameters before executing the database query.
Save your changes and test retrieving all posts:
You should see all the posts you've created so far:
You can also test filtering posts by their publication status:
This should return only unpublished posts (which is an empty array since all your posts are published):
Now that you've implemented and tested the endpoint for retrieving all posts let's add another endpoint to fetch a single post by its ID. Update your routes/posts.js file again:
This new endpoint retrieves a single post by its ID. It uses Prisma's findUnique method for an efficient primary key lookup. If no post is found, it returns a 404 Not Found response.
The schema option applies the getPostSchema you defined earlier, which validates that the ID is a valid UUID before attempting to retrieve the post.
Save your changes and test retrieving a single post by its ID (replace with an actual ID from your database):
You should receive the specific post:
Try requesting a non-existent post to see the error handling:
You should receive a validation error because the provided ID doesn't match the UUID format:
With these GET endpoints implemented, your API supports creating and retrieving blog posts. Next, you'll implement the PUT endpoint to update existing posts.
Step 6 — Updating blog posts
After implementing creation and retrieval functionality, you'll now add the ability to update existing posts. In RESTful design, PUT requests replace the entire resource, allowing changes to the title, content, or publication status.
Let's update your routes/posts.js file to add a PUT method for updating posts:
This PUT endpoint uses the updatePostSchema to validate both the URL parameter and the request body. The handler follows a two-step process:
- First, it verifies that the post exists using
findUnique - Then it updates the post with the new data using Prisma's
updatemethod
The implementation preserves the published status if it's not included in the request, which allows for partial updates. This approach ensures that the post exists before attempting to update it, providing better error handling for API consumers.
Save your changes and test the endpoint by updating one of the posts you created earlier. Replace the ID in the URL with an actual ID from your database:
You should receive a response with the updated post:
Notice that while the createdAt timestamp remains the same, the updatedAt timestamp has been updated to reflect the modification time.
You can also try updating the publication status of a post. This is useful for changing a draft post to published or vice versa:
The response should show the post with its publication status changed to false:
Test validation by attempting to update with invalid data, such as a title that's too short:
You should receive a validation error:
With the PUT endpoint implemented, your API now supports updating existing blog posts. In the next step, you'll complete the CRUD functionality by implementing the DELETE endpoint.
Step 7 — Removing blog posts
The final step in completing the CRUD functionality of your blog API is to implement the DELETE endpoint. This will allow users to remove posts they no longer need from the database permanently.
Let's update your routes/posts.js file to add a DELETE method:
This endpoint allows users to remove a blog post from the database permanently. It follows RESTful conventions by:
- Using the DELETE HTTP method for resource removal
- Validating the post ID with the
deletePostSchema - Checking if the post exists before attempting to delete it
- Returning a
204 No Contentstatus code on success, indicating the request was successful but no content is being returned - Providing appropriate error responses for "not found" and server error situations
The implementation uses a find-then-delete pattern, which gives you better control over error handling by confirming the resource exists before attempting to delete it.
Save your changes and test the DELETE endpoint. First, let's create a new post specifically for testing deletion:
You should receive a response with the newly created post, including its ID. Copy this ID and use it in the following command:
The -v (verbose) flag will show the response headers, including the status code. You should see a 204 No Content response:
The HTTP status code 204 confirms that the post was successfully deleted. To verify further, try to fetch the deleted post:
You should receive a 404 Not Found response:
Congratulations! You've successfully built a complete RESTful API for blog posts using Fastify and Prisma with SQLite.
Final thoughts
This guide showed you how to set up Fastify with Prisma to build a fast and reliable API. You’ve added JSON Schema validation, organized routes with plugins, and handled errors properly—essential steps for a production-ready project.
As your API grows, you can add authentication, database relationships, pagination, rate limiting, and API documentation. Fastify’s design makes it an excellent choice for handling high traffic while keeping your code easy to manage.
For more details, check out the official documentation for Fastify and Prisma. You might also find Fastify’s plugin ecosystem helpful in adding features like authentication with @fastify/jwt or API documentation with @fastify/swagger.