If you’re an API developer working with Node.js, then you’re probably familiar with Express. But have you tried out the Fastify framework to build with power, speed, and convenience? In this walkthrough, we build a full-featured, easy-to-consume API with Fastify. And we deploy it to the cloud with ease. We show you how to:
- Get started working with Fastify to build an API
- Implement API authentication by using a JSON web token (JWT)
- Use Fastify’s Swagger plugins to generate an OpenAPI specification
- Consume the OpenAPI specification with Postman, giving you an API client that can send requests seamlessly to your back-end API
- Deploy your application to Heroku
This project is part of our Heroku Reference Applications GitHub organization where we host different projects showcasing architectures and patterns to deploy to Heroku.
Key Concepts
Before we code, let’s briefly cover the core concepts and technologies for this walkthrough.
Application Flow
A Heroku Postgres database stores records of usernames, first names, last names, and emails in a users table. The public endpoint of our API (/directory
) returns a list of usernames for all users in the table. The protected endpoint (/profile
) requires a JWT with username
in the payload. This endpoint returns additional information about the user with the given username.
What's Fastify?
Fastify is a web framework for Node.js that boasts speed, low overhead, and a delightful developer experience. Many Node.js developers have adopted Fastify as an alternative to Express.
Fastify is designed with a plugin architecture, making it incredibly modular. Its documentation says that “in Fastify everything is a plugin.” This architecture makes it easy for developers to build and use utilities, middleware, and other niceties. We dive deeper into working with plugins as we get to coding.
Authentication
Our authenticated route requires a JWT signed with an RSA256 private key. We attach that JWT, and the API uses the symmetric public key to validate it.
The username
in the payload of the validated JWT is meant to represent the user making the request, so the /profile
endpoint returns account information about that user.
API Documentation
We also document our API routes as we write our code. Fastify has OpenAPI support through its plugin ecosystem that generates the full OpenAPI specification and gives us a UI. With the OpenAPI specification generated, we can also use Postman to import the spec to give us a client that can send requests to our API.
Deployment
After doing a little bit of local testing, we can deploy our API to Heroku with just a few quick CLI commands, or the Deploy to Heroku button in the GitHub repository
Get Started
To use this demo, you need:
- A Heroku account. You must add a payment method to cover your compute and database costs. To run this API, go with the Eco dyno, which has a $5 monthly flat fee. You also need a Heroku Postgres instance. Go with the Mini plan, at a max of $5 monthly cost. The Eco and Mini plans are enough for this sample application.
- A GitHub account for your code repository. Heroku hooks into your GitHub repo directly, simplifying deployment to a single click.
- (Optional) The Postman client application installed on your local machine. You need Postman to follow along in our section on importing an OpenAPI specification.
You can start by cloning the GitHub repo for this project. If you simply want to deploy and start using the API, follow the instructions in the README.
To keep this walkthrough simple, we’re going to highlight the most important parts of the code to help you understand how we built this API. We don’t go through everything line by line, but you can always reference the repo codebase to examine the code itself.
Initialize the Project
When building this project, we used Node v20.11.1 along with npm as our package manager. Start by initializing a new project and installing dependencies:
npm init -y
npm install fastify fastify-cli fastify-plugin @fastify/auth @fastify/autoload @fastify/jwt @fastify/swagger @fastify/swagger-ui fast-jwt dotenv pg
Create the Initial app.js
File
Just to start things out, we begin with an app.js
file in our project root folder. This file is our “hello world” initial application:
app.js
export default async (fastify, opts) => {
fastify.get(
"/",
async function (_request, reply) {
reply.code(200).type("text/plan").send("hello world");
},
);
}
We use the fastify-cli
to run the app.js
file. Notice that we don’t need to import Fastify
in our file, since we pass an instance of a Fastify server
object, fastify
, to the function as an argument. To start, we add handling for a GET
request to /
. As we build up our API, we can simply enhance this instance by registering new plugins.
Let’s add some lines to our package.json
file to use that app.js
file.
package.json
{
"name": "openapi-fastify-jwt",
"version": "1.0.0",
"type": "module",
"description": "A sample Fastify API with RSA256 JWT authentication",
"main": "app.js",
"scripts": {
"start": "fastify start -a 0.0.0.0 -l info app.js",
"dev": "fastify start -w -l info -P app.js"
},
The fastify-cli
command in our scripts
section starts up our server to listen for requests. We start our local server like this:
npm run dev
[10:22:17.323] INFO (816073): Server listening at http://127.0.0.1:3000
In a separate terminal window, we test our server:
curl localhost:3000
hello world
Create the Database Plugin
Next, we write a plugin for querying our Postgres database, and add it to our fastify
instance.
In a subfolder called plugins
, we create a file called db.js
with the following contents:
plugins/db.js
import fp from "fastify-plugin";
import pg from "pg";
const { Pool } = pg;
export default fp(async (fastify) => {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false,
},
});
fastify.decorate("db", {
query: async (text, params) => {
const result = await pool.query(text, params);
return result.rows;
},
});
})
The standard convention for creating Fastify plugins uses the fastify-plugin package,imported above as a function called fp
. We define how to enhance our fastify
instance, then call fp()
on that functionality and export it.
Note: The Fastify ecosystem has its own @fastify/postgresql plugin which is the recommendation for production-based applications. We decided to build our own plugin to demonstrate how to extend Fastify with a simple plugin.
Our database plugin opens a connection to a Postgres database based on the DATABASE_URL
environment variable. We have a method called query
which sends the SQL query along with any parameters, returning the result.
Notice that we decorate
our fastify
instance with the string db
, supplying the definition for our query
function. By doing this, we can call fastify.db.query
for any fastify
instance that registered this plugin.
Back in app.js
, let’s register our newly created plugin. We could call fastify.register
individually on each plugin we want to register, as Fastify’s getting started guide describes. However, we use @fastify/autoload to quickly register all plugins in a given folder. Our app.js
file now looks like this, after removing the GET
handler for /
:
app.js
import path from "path";
import AutoLoad from "@fastify/autoload";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default async (fastify, opts) => {
fastify.register(AutoLoad, {
dir: path.join(__dirname, "plugins"),
options: Object.assign({}),
});
};
By using autoload, we register any plugins found in our plugins
subfolder.
Create the /directory
Route
Next, we add our /directory
route. This public route returns all the usernames
in our database’s users
table. The handler uses our db
plugin’s query
method.
In a subfolder called routes
, we create a file called directory.js
with the following contents:
routes/directory.js
export default async function (fastify, _opts) {
fastify.get(
"/directory",
async (_request, reply) => {
const { db } = fastify;
const rows = await db.query(
"SELECT username FROM users ORDER by username",
);
const records = rows.map((r) => { username: r.username });
reply.code(200).type("application/json").send(records);
},
);
}
Notice how we use the db
object from our fastify
instance. This code assumes that our fastify
instance registered a plugin that decorates the instance with db
, giving us convenient access to db.query
. We handle GET
requests to /directory
by making the appropriate query and returning the results.
Back in app.js
, we have to make sure to add this route to our fastify
instance by calling fastify.register
. Just like we did for our plugins
subfolder, we autoload any files in our routes
subfolder. Let’s also add in a call to dotenv
, since we need our DATABASE_URL
environment variable soon.
app.js
import "dotenv/config";
import path from "path";
import AutoLoad from "@fastify/autoload";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default async (fastify, opts) => {
fastify.register(AutoLoad, {
dir: path.join(__dirname, "plugins"),
options: Object.assign({}),
});
fastify.register(AutoLoad, {
dir: path.join(__dirname, "routes"),
options: Object.assign({}),
});
};
Set Up a Local Postgres Database
For local testing, we set up a local Postgres database. Then, we add the database’s connection string to a file called .env
in the project root folder. For example:
.env
DATABASE_URL=postgres://user:password@localhost:5432/my_database
You can use files from the repository codebase (in the data
subfolder) to create the database schema and seed the table with records.
psql \
postgres://user:password@localhost:5432/my_database \
< create_schema.sql
psql \
postgres://user:password@localhost:5432/my_database \
< create_records.sql
With the database plugin, public /directory
route, and local database all in place, we test our server again. We start our server with npm run dev
. Then, in a separate terminal window:
curl localhost:3000/directory
[{"username":"adelia.casper"},{"username":"aisha.upton"},{"username":"alfred.lindgren"},{"username":"alysha.mclaughlin"},{"username":"angie.keebler"},{"username":"antonia.gutmann"},{"username":"baron.hessel"},{"username":"bernadine.powlowski"},{"username":"carlee.abbott"},{"username":"charley.glover"},{"username":"cora.bednar"},{"username":"darryl.reynolds"},{"username":"dee.gorczany"},{"username":"dennis.koss"},{"username":"deshaun.wiza"},{"username":"devante.lakin"},{"username":"edythe.thompson"},{"username":"eldon.bahringer"},{"username":"elenor.trantow"},{"username":"elijah.hane"},{"username":"erin.haley"},{"username":"estefania.will"},{"username":"haven.rippin"},{"username":"houston.rowe"},{"username":"imani.okon"},{"username":"irma.durgan"},{"username":"jaiden.vandervort"},{"username":"jamar.maggio"},{"username":"jamir.walsh"},{"username":"jedediah.mraz"},{"username":"jett.beier"},{"username":"johnathon.hessel"},{"username":"jovan.turner"},{"username":"kade.hilpert"},{"username":"king.berge"},{"username":"laurie.marquardt"},{"username":"madge.hettinger"},{"username":"magali.terry"},{"username":"magdalena.farrell"},{"username":"marty.wunsch"},{"username":"mellie.donnelly"},{"username":"muriel.walker"},{"username":"noelia.jenkins"},{"username":"nolan.dubuque"},{"username":"otis.grady"},{"username":"rene.bins"},{"username":"rhoda.bashirian"},{"username":"rose.boehm"},{"username":"tatyana.wolf"},{"username":"zion.reichel"}]%
Excellent. Our public route and our database plugin look like they’re working. Now, it’s time to move onto authentication.
Create the Authentication Plugin
In our plugins
subfolder, we create a new plugin in auth.js
. It looks like this:
plugins/auth.js
import fp from "fastify-plugin";
import jwt from "@fastify/jwt";
import auth from "@fastify/auth";
export default fp(async (fastify) => {
if (!process.env.RSA_PUBLIC_KEY_BASE_64) {
throw new Error(
"Environment variable `RSA_PUBLIC_KEY_BASE_64` is required",
);
}
const publicKey = Buffer.from(
process.env.RSA_PUBLIC_KEY_BASE_64,
"base64",
).toString("ascii");
if (!publicKey) {
fastify.logger.error(
"Public key not found. Make sure env var `RSA_PUBLIC_KEY_BASE_64` is set.",
);
}
fastify.register(jwt, {
secret: {
public: publicKey,
},
});
fastify.register(auth);
fastify.decorate("verifyJWT", async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
});
Our authentication process checks that the supplied JWT is properly signed. We verify the signature with the signer’s public key. Let’s walk through what we’re doing here step by step:
- Read in the
publicKey
from ourRSA_PUBLIC_KEY_BASE_64
environment variable. The key must be in base64 format. - Register the
@fastify/jwt
plugin, supplying thepublicKey
because we use the plugin in verify-only mode. - Register the @fastify/auth plugin, which adds convenience utilities for attaching authentication to routes.
- Decorate our
fastify
instance with a function calledverifyJWT
. Our function calls thejwtVerify
function in the@fastify/jwt
plugin, passing it the API request. That function checks theAuthorization
header for a bearer token and verifies the JWT against ourpublicKey
.
Because our app.js
file already autoloads any plugins in our plugins
subfolder, we don’t need to do anything else to register our new authentication plugin.
Create the Authenticated /profile
Route
In our routes
subfolder, we create a file called profile.js
with the following contents:
routes/profiles.js
export default async function (fastify, _opts) {
fastify.get(
"/profile",
{
onRequest: [fastify.auth([fastify.verifyJWT])],
},
async (request, reply) => {
const { db } = fastify;
const sql =
'SELECT id, username, first_name as "firstName", last_name as "lastName", email FROM users WHERE username=$1';
const rows = await db.query(sql, [request.user.username]);
if (rows.length) {
reply.code(200).type("application/json").send(rows[0]);
} else {
reply.code(404).type("text/plain").send("Not Found");
}
},
);
}
How we implement this route differs slightly from that of /directory
. When calling fastify.get
, we include an object with route options as the second argument, before our handler function definition. We include the onRequest
option, which acts like middleware handling. When a request to /profile
comes in, Fastify calls fastify.auth
for authentication, passing it our decorated fastify.verifyJWT
function as our authentication strategy.
For our route handler, notice that our SQL query references request.user.username
. You might wonder where that came from. Do you remember how we expect the JWT payload to include a username
? When the @fastify/jwt
plugin verifies the JWT, it writes the JWT payload to a user
object in the request, passing that payload information downstream. That gives us access to request.user.username
in our route handler. We call our database plugin to query for the user’s information, and we send the response.
And, because app.js
autoloads the routes
subfolder, our server is immediately serving up this route.
Generating Keys and Tokens
When we deploy our API, we use a new pair of public/private RSA keys. You can generate a pair online here. You need the public key, in base64 format, as an environment variable for JWT verification. You only use the private key when signing a JWT for accessing the API’s authenticated route.
Our codebase provides a utility for generating a JWT and signing it with a private key. Here’s an example of how to use it:
npm run generate:jwt \
utils/keys/private_key.example.rsa \
'{"username":"aisha.upton"}'
Token: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFpc2hhLnVwdG9uIiwiaWF0IjoxNzE0NDEzNzk3fQ.U0Nkb5IIDKjGv2VHFZQZE8nMpDbj25ui1b868lAnLU5T_rUcsYq-oq792gFlHcMdYmYZ92eHfqEVKjqEcKbeVRCrWSUi3pm0BN74cXZ8Q0DWc1EdxxsgtxdPZ9jtckUkeCG9BNsMBbCAQfSb_cURq4hbX9js28DYP3sVuc5soKE
With a valid token, we can test our server’s authenticated route:
# Valid token
curl \
--header "Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFpc2hhLnVwdG9uIiwiaWF0IjoxNzE0NDEzNzk3fQ.U0Nkb5IIDKjGv2VHFZQZE8nMpDbj25ui1b868lAnLU5T_rUcsYq-oq792gFlHcMdYmYZ92eHfqEVKjqEcKbeVRCrWSUi3pm0BN74cXZ8Q0DWc1EdxxsgtxdPZ9jtckUkeCG9BNsMBbCAQfSb_cURq4hbX9js28DYP3sVuc5soKE" \
localhost:3000/profile
{"id":"402b11d2-20a0-4104-9800-9b5b9dee4dc1","username":"aisha.upton","firstName":"Aisha","lastName":"Upton","email":"aisha.upton@example.com"}%
Our authentication works!
Here are some examples of how the @fastify/auth
and @fastify/jwt
plugins handle bad requests, just to show how it looks:
# No token
curl localhost:3000/profile
{"statusCode":401,"code":"FST_JWT_NO_AUTHORIZATION_IN_HEADER","error":"Unauthorized","message":"No Authorization was found in request.headers"}
# Invalid token
curl --header "Authorization:Bearer this-is-not-a-valid-token" localhost:3000/profile
{"statusCode":401,"code":"FST_JWT_AUTHORIZATION_TOKEN_INVALID","error":"Unauthorized","message":"Authorization token is invalid: The token is malformed."}
# Token signed by a different key
curl \
--header "Authorization:Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6IkNydXoxOSIsImlhdCI6MTcwOTMyMTc4Mn0.YWklNLXmojxc7Kg0M0utMHQGylsUK3LrHozvcVPYHCvZIG-nwJKKSW9FKzQ9I0glxZdWvjELGwoP7uWVGHyyEo7c3HTk1pxG-av7T9CmWf_Gk0D58n1T1PkeO7YqE-2JL6vIlvnAiUQRrrknYlEAc8Z3UruYik_CFqoRxbLkZl8" \
localhost:3000/profile
{"statusCode":401,"code":"FST_JWT_AUTHORIZATION_TOKEN_INVALID","error":"Unauthorized","message":"Authorization token is invalid: The token signature is invalid."}
Use OpenAPI and Swagger UI for Documentation
With Fastify, we can take advantage of the @fastify/swagger
and @fastify/swagger-ui
plugins to conveniently generate an OpenAPI specification for our API.
First, we define our data model schemas (in schemas/index.js
) using the Validation and Serialization feature from Fastify.
Next, in app.js
, we register the @fastify/swagger
plugin and supply it with general information about our server. We also register the @fastify/swagger-ui
, providing a path (/api-docs
). This plugin creates an entire Swagger UI with our OpenAPI specification at that path. Our final app.js
file looks like this:
app.js
import "dotenv/config";
import path from "path";
import AutoLoad from "@fastify/autoload";
import Swagger from "@fastify/swagger";
import SwaggerUI from "@fastify/swagger-ui";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const options = {};
export default async (fastify, opts) => {
fastify.register(Swagger, {
openapi: {
info: {
title: "User Directory and Profile",
description:
"Demonstrates Fastify with authenticated route using RSA256",
version: "1.0.0",
},
components: {
securitySchemes: {
BearerAuth: {
description:
"RSA256 JWT signed by private key, with username in payload",
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
},
fastifys: [
{
url: "http://localhost:8080",
},
],
tags: [
{
name: "user",
description: "User-related endpoints",
},
],
},
refResolver: {
buildLocalReference: (json, _baseUri, _fragment, _i) => {
return json.$id || `def-{i}`;
},
},
});
fastify.register(SwaggerUI, {
routePrefix: "/api-docs",
});
fastify.register(AutoLoad, {
dir: path.join(__dirname, "plugins"),
options: Object.assign({}),
});
fastify.register(AutoLoad, {
dir: path.join(__dirname, "routes"),
options: Object.assign({}),
});
};
We also want to add OpenAPI specification info for each of our routes. As an example, here is how we do it in routes/profile.js
:
routes/profile.js
import {
profileSchema,
errorSchema,
} from "../schemas/index.js";
export default async function (fastify, _opts) {
fastify.addSchema({
$id: "profile",
...profileSchema,
});
fastify.addSchema({
$id: "error",
...errorSchema,
});
fastify.get(
"/profile",
{
schema: {
description:
"Get user's own profile with additional account attributes",
tags: ["user"],
security: [
{
BearerAuth: [],
},
],
response: {
200: {
description: "User profile",
$ref: "profile#",
},
404: {
description: "Not Found",
$ref: "error#",
},
500: {
description: "Internal Server Error",
$ref: "error#",
},
},
},
onRequest: [fastify.auth([fastify.verifyJWT])],
},
async (request, reply) => {
…
},
);
}
In this file, we add a schema
object to our route options argument. In line with how OpenAPI specifications are written, we add information regarding security, responses, and so on. We do something similar in routes/directory.js
.
Now, when we spin up our server, we can visit http://localhost:3000/api-docs to see this:
From right within the Swagger UI, we can send requests to our API. For example, we can use the JWT we generated earlier and send an authenticated request to /profile
.
Import OpenAPI Specification into Postman
The Swagger UI is nice, but we can also use Postman for better programmatic usage and developer experience when it comes to authentication.
In Postman, we click the Import button.
We can import our OpenAPI specification using a URL. Our Swagger UI shows that the specification is available at http://localhost:3000/api-docs/json. We provide this URL to Postman, choosing to import the API as a Postman Collection.
Now, we have a new collection in Postman with requests set up to hit our API:
When we click on the profile’s GET
request, and then click on the Authorization tab, we see that Postman expects two variables: baseUrl
and bearerToken
.
Let’s set the values for those. Go to the options for our Postman Collection, navigating to the Variables tab. There, we set baseUrl
to http://localhost:3000
. Then, we add a new variable called bearerToken
, and we use the value of the valid JWT generated earlier.
Click Save in the upper-right corner. Then, we go back to our /profile
request and click Send.
Going from our OpenAPI specification to Postman is so quick and easy!
Deploy the API to Heroku
As an API developer, you want to spend your development time focused on building and coding. Ideally, deploying your APIs is fast and painless. With Heroku, it is!
Assuming you installed the Heroku CLI, here’s how to deploy your API.
Step 1: Log in
heroku login
Step 2: Create a new app
heroku create my-fastify-api
Creating ⬢ my-fastify-api... done
https://my-fastify-api-58737de5faf0.herokuapp.com/ | https://git.heroku.com/my-fastify-api.git
Step 3: Add the Heroku Postgres add-on
heroku addons:create heroku-postgresql
Creating heroku-postgresql on ⬢ my-fastify-api... ~$0.007/hour (max $5/month)
Database has been created and is available
Step 4: Load the database schema and seed data
heroku pg:psql < data/create_schema.sql
CREATE TABLE
heroku pg:psql < data/create_records.sql
INSERT 0 50
Step 5: Add your RSA public key as a config variable
heroku config:set \
RSA_PUBLIC_KEY_BASE_64=`cat utils/keys/public_key.example.rsa | base64`
Setting RSA_PUBLIC_KEY_BASE_64 and restarting ⬢ my-fastify-api... done
Step 6: Create a Git remote to point to Heroku
heroku git:remote -a my-fastify-api
set git remote heroku to https://git.heroku.com/my-fastify-api.git
Step 7: Push your repository branch to Heroku
git push heroku main
…
remote: -----> Creating runtime environment
…
remote: -----> Installing dependencies
…
remote: -----> Build succeeded!
…
remote: -----> Launching...
remote: Released v6
remote: https://my-fastify-api-58737de5faf0.herokuapp.com/ deployed to Heroku
…
That’s it! Just a few commands in the Heroku CLI, and our API is deployed, configured, and running. Let’s do some checks to make sure.
At the command line, with curl
:
curl https://my-fastify-api-58737de5faf0.herokuapp.com/directory
[{"username":"adelia.casper"},{"username":"aisha.upton"},{"username":"alfred.lindgren"},{"username":"alysha.mclaughlin"},{"username":"angie.keebler"},{"username":"antonia.gutmann"},{"username":"baron.hessel"},{"username":"bernadine.powlowski"},{"username":"carlee.abbott"},{"username":"charley.glover"},{"username":"cora.bednar"},{"username":"darryl.reynolds"},{"username":"dee.gorczany"},{"username":"dennis.koss"},{"username":"deshaun.wiza"},{"username":"devante.lakin"},{"username":"edythe.thompson"},{"username":"eldon.bahringer"},{"username":"elenor.trantow"},{"username":"elijah.hane"},{"username":"erin.haley"},{"username":"estefania.will"},{"username":"haven.rippin"},{"username":"houston.rowe"},{"username":"imani.okon"},{"username":"irma.durgan"},{"username":"jaiden.vandervort"},{"username":"jamar.maggio"},{"username":"jamir.walsh"},{"username":"jedediah.mraz"},{"username":"jett.beier"},{"username":"johnathon.hessel"},{"username":"jovan.turner"},{"username":"kade.hilpert"},{"username":"king.berge"},{"username":"laurie.marquardt"},{"username":"madge.hettinger"},{"username":"magali.terry"},{"username":"magdalena.farrell"},{"username":"marty.wunsch"},{"username":"mellie.donnelly"},{"username":"muriel.walker"},{"username":"noelia.jenkins"},{"username":"nolan.dubuque"},{"username":"otis.grady"},{"username":"rene.bins"},{"username":"rhoda.bashirian"},{"username":"rose.boehm"},{"username":"tatyana.wolf"},{"username":"zion.reichel"}]%
In Postman, with an updated baseUrl
to point to our Heroku app URL (while keeping the valid bearerToken
):
And finally, in our browser, checking out the API docs:
Conclusion
When building a Node.js API, using the Fastify framework helps you get up and running quickly. You have access to a rich ecosystem of existing plugins, and building your own plugins is simple and straightforward too. Here’s a quick rundown of everything we did in this walkthrough:
- Used Fastify to build an API server
- Built plugins for database querying and JWT authentication
- Built two routes (one public, one protected) for our API
- Integrated OpenAPI-related plugins to get an OpenAPI specification and a Swagger UI
- Showed how to import our OpenAPI specification into Postman
- Deployed our API to Heroku with just a few commands
With technologies like Fastify, JSON web tokens, and OpenAPI, you can quickly build APIs that are powerful, secure, and easy to consume. Then, when it’s time to deploy and run your code, going with Heroku gets you up and running — _within minutes — _at a low cost. When you’re ready to get started, sign up for a Heroku account and begin building today!