REST has grown steadily over the years and has become the ideal mode of system interaction over the internet. It’s grown to have a great advantage over the older kids, SOAP and XML-RPC. Thousands of companies and startups are using REST.
REST is preferred because it enables devs and users to predict the action to take, its action on the datastore, and the data they will receive. REST API is an interface in which Mobile, Desktop, and Web systems can interact with a server to perform CRUD actions.
Node.js has many web frameworks we can use to build REST APIs, but today we will be learning about a new Node.js web framework, Fastify.
Fast and low overhead web framework for Node.js
- Get Fastify with NPM: “npm install fastify.” Then, create server.js and add the following content: // Require the framework… www.fastify.io
Fastify is a high-performance HTTP web framework for Nodejs. It is largely inspired by Express.js and Hapi.js, especially in the routing and Middleware systems. Fastify is more like a Chimera of Express.js and Hapi.js in one package.
We will build a basic Users CRUD API to demonstrate how we can use Fastify to:
In this post, we will learn:
We will need the following utilities:
We will need the following NPM dependencies:
A good understanding of JavaScript and REST APIs is also required.
We will set up our project and install the above dependencies:
$ mkdir fastify-prj
$ cd fastify-prj
$ npm init -y
$ touch index.js
What did we do?
Simply, what we added are bash command types in the bash terminal. Following the commands from the top, we created a folder named “fastify-prj” where our server code will be built. Then, we moved into the folder, initialized a Node environment inside the folder, and created an “index.js” file. This is where we will type our code.
Let’s install the dependencies.
First, the Fastify web framework:
$ npm i fastify --save
This command installs the Fastify library.
Next, the Mongoose library:
$ npm i mongoose --save
Note: The
--save
flag installs them as the main dependency of the project.
With that, our file structure will look like this:
fastify-prj/
node_modules/
- index.js
- package.json
Now that we have everything installed, let’s set up our Fastify server and create our first route.
Open the index.js
and type in the following code:
// index.js
// Import the fastify framework
const fastify = require('fastify')
const app = fastify()
// Set a GET route "/"
app.get('/', function (request, reply) {
reply.send("Our first route")
})
// Start the server
app.listen(3000, function (err, address) {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`Server listening on ${address}`)
})
The above code will set up a simple Fastify server.
First, we imported the Fastify web framework with the require("fastify)
statement. Then, we called the Fastify function fastify()
and set the returned value to the app
variable. Fastify APIs will be available in this variable.
Next, we set up a GET route at our server’s /
path by calling the get()
method, just like we do in Express.js. The first argument is the path of the API endpoint.
In this case it is /
.
The second argument is a handler function. This function will be executed when an HTTP GET method with path /
is sent to the server. In our case, this handler function replies with an Our first route
text to the user.
Handler functions take two arguments: a reply
and a request
object. The reply
object is used to send messages back to the browser or anything called an endpoint. The request
object contains methods and properties used to get data from the HTTP request from the browser or anything called the endpoint.
In this case, we used the send
method in reply
to send the text Our first route
back to our caller.
Further down in the code, we call the listen()
method, passing a port number 3000
and a callback function. The callback function takes two arguments. The first is an error, and the second is the server’s address. The listen()
method will spawn a process and listen for TTP
requests on port 3000
.
To start our server, run the below command in your terminal:
$ node index.js
We will see a “Server listening on http://localhost:3000
" display in our terminal:
$ node index.js
Server listening on http://localhost:3000
Open your browser and navigate to http://localhost:3000
. You will see Our first route
displayed in your browser.
Using cURL, we will still see the Our first route
displayed on the terminal:
$ curl localhost:3000
Our first route
```sh
We have demonstrated how to create a simple endpoint using Fastify. Now, we will move on to create our users API endpoints. We will see how we can use the database (MongoDB) with Fastify.
Our users API will have the endpoints:
/api/users
GET: Returns all users in the datastore./api/users/:userId
GET: Returns a specific user./api/users
POST: Adds a new user./api/users/:userId
PUT: Edits a user./api/users/:userId
DELETE: Removes a user.Now, let’s quickly create a Mongoose User schema:
touch User.js
We created a User.js
file, and this will contain a Mongoose schema for a user:
// User.js
const mongoose = require('mongoose')
let UserSchema = new mongoose.Schema({
name: String,
age: Number,
email: String
})
module.exports = mongoose.model('User', UserSchema)
The UserSchema
contains information relating to a single user.
Now, we import the Mongoose library and connect it to the MongoDB server. We will do this in the index.js file:
// index.js
// Import the fastify framework
const fastify = require('fastify')
// Import "mongoose"
const mongoose = require("mongoose")
const app = fastify()
const mongoUrl = process.env.MONGODB_URI || "mongodb://localhost:27017/users"
/** connect to MongoDB datastore */
try {
mongoose.connect(mongoUrl)
} catch (error) {
console.error(error)
}
// Set a GET route "/"
app.get('/', function (request, reply) {
reply.send("Our first route")
})
// Start the server
app.listen(3000, function (err, address) {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`Server listening on ${address}`)
})
```js
Next, we import our User schema.
```js
// index.js
// Import the fastify framework
const fastify = require('fastify')
// Import "mongoose"
const mongoose = require("mongoose")
// Import our "User" model
const User = require("./User")
const app = fastify()
...
We code our endpoints:
/api/users
GET...
app.get("/api/users", (request, reply) => {
User.find({}, (err, users) => {
if(!err) {
reply.send(users)
} else {
reply.send({ error: err })
}
})
})
...
We have our first endpoint, /api/users GET
, to get all users in the datastore. We called User. find({})
to return all users to the database and then send it to the user.
api/users/:userId
GET...
app.get("/api/users/:userId", (request, reply) => {
var userId = request.params.userId
User.findById(userId, (err, user) => {
if(!err) {
reply.send(user)
} else {
reply.send({ error: err })
}
})
})
...
Here, we use a parametric path to get a specific user from the API. The :userId
in the /api/users/:userId
path holds the specific user’s ID that we want to retrieve.
Like Express.js, Fastify will map the userId
to the request.params
object body to retrieve a user ID by referencing the request.params
with userId
, like this:
request.params.userId
So, we retrieve the user ID from request.params
and use it alongside the Mongoose User.findById
method to retrieve only the user record from the database. After the user is retrieved from the database, we send it using request.send()
.
Now we can move onto the next endpoint.
/api/users
POST...
app.post("/api/users", (request, reply) => {
var user = request.body
User.create(user, (err, user) => {
if(!err) {
reply.send(user)
} else {
reply.send({ error: err })
}
})
})
...
This endpoint will be an HTTP POST verb, so that is why we called the post()
method on the Fastify instance app
. This will set up the /api/users
endpoint to listen for a POST request and execute the handler function.
In this endpoint, it will create a new user. The user’s details will be in the request.body
object. So, we retrieved it in the user
variable, then called User.create
with it to create a user with the request details in the database. The handler function will return the created user if successful.
/api/users/:userId
PUT...
app.put("/api/users/:userId", (request, reply) => {
var userId = request.params.userId
var newUserEdit = request.body
User.findById(userId, (err, user) => {
if(!err) {
user.age = newUserEdit.age
user.name = newUserEdit.name
user.email = newUserEdit.email
user.save((er, savedUser) => {
if(!er) {
reply.send(savedUser)
} else {
reply.send(er)
}
})
} else {
reply.send({ error: err })
}
})
})
...
In this endpoint, it will edit an existing user. The PUT HTTP verb is used to denote an editing endpoint. So, we have a parametric path there, with :userId
holding the specific ID of the user to be edited/updated. The request body will then hold the data to be updated.
We retrieve the user by calling User.finById
with the userId
as param
. The callback will then accept the retrieved user, and we then update the user properties with the data in the request body. Then, finally, we save the edited user by calling the save()
method on the user
. This puts the user back in the database with the updated properties.
/api/user/:userId
DELETE...
app.put("/api/users/:userId", (request, reply) => {
var userId = request.params.userId
User.findById(userId, (err, user) => {
if(!err) {
user.remove((er) => {
if(!er) {
reply.send("USER DELETED")
} else {
reply.send({ error: er })
}
})
} else {
reply.send({ error: err })
}
})
})
...
This endpoint will delete a user from the database. It is also a parametric path with a :userId
that holds the user’s ID to be removed.
We get the ID from the request.params
and get the user to be removed with the User.findById
method. The callback will hold the user in its user
argument, and then we delete the user by calling the remove()
method in the returned user
. This deletes/removes the user from the database.
We are down with the endpoints. We also have to remove our initial demo route /
, just so the code does not confuse us.
// index.js
// Import the fastify framework
const fastify = require('fastify')
// Import "mongoose"
const mongoose = require("mongoose")
// Import our "User" model
const User = require("./User")
const app = fastify()
const mongoUrl = process.env.MONGODB_URI || "mongodb://localhost:27017/users"
/** connect to MongoDB datastore */
try {
mongoose.connect(mongoUrl)
} catch (error) {
console.error(error)
}
app.get("/api/users", (request, reply) => {
User.find({}, (err, users) => {
if (!err) {
reply.send(users)
} else {
reply.send({ error: err })
}
})
})
app.get("/api/users/:userId", (request, reply) => {
var userId = request.params.userId
User.findById(userId, (err, user) => {
if (!err) {
reply.send(user)
} else {
reply.send({ error: err })
}
})
})
app.post("/api/users", (request, reply) => {
var user = request.body
User.create(user, (err, user) => {
if (!err) {
reply.send(user)
} else {
reply.send({ error: err })
}
})
})
app.put("/api/users/:userId", (request, reply) => {
var userId = request.params.userId
var newUserEdit = request.body
User.findById(userId, (err, user) => {
if (!err) {
user.age = newUserEdit.age
user.name = newUserEdit.name
user.email = newUserEdit.email
user.save((er, savedUser) => {
if (!er) {
reply.send(savedUser)
} else {
reply.send(er)
}
})
} else {
reply.send({ error: err })
}
})
})
app.put("/api/users/:userId", (request, reply) => {
var userId = request.params.userId
User.findById(userId, (err, user) => {
if (!err) {
user.remove((er) => {
if (!er) {
reply.send("USER DELETED")
} else {
reply.send({ error: er })
}
})
} else {
reply.send({ error: err })
}
})
})
// Start the server
app.listen(3000, function (err, address) {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`Server listening on ${address}`)
})
Before starting our server, we need to power up the Mongo server. To do that, type the this command in your terminal:
$ mongod
Then, in another terminal instance, run this server:
$ node index
Server listening on http://localhost:3000
We have a fully functional User API. We can now test our API endpoints.
For testing, we will use the cURL. Let’s start with the /api/users
$ curl localhost:3000/api/uses -X POST --data "{'name': 'nnamdi', 'age': 20, 'email': 'kurtwanger40@gmail.com'}"
{name: 'nnamdi', age: 20, email: 'kurtwanger40@gmail.com', _id: '5fa23434fda5643434'}
This creates a new user {'name': 'nnamdi', 'age': 20, 'email': 'kurtwanger40@gmail.com'}
.
Let’s add a second user:
$ curl localhost:3000/api/uses -X POST --data "{'name': 'chidume', 'age': 27, 'email': 'kurtwanger5@gmail.com'}"
{name: 'chidume', age: 27, email: 'kurtwanger5@gmail.com', _id: 'a56434345fa23434fd'}
Let’s test the /api/users
$ curl localhost:3000/api/users
[
{name: 'nnamdi', age: 20, email: 'kurtwanger40@gmail.com', _id: '5fa23434fda5643434'},
{name: 'chidume', age: 27, email: 'kurtwanger5@gmail.com', _id: 'a56434345fa23434fd'}
]
Let’s get specific users:
$ curl localhost:3000/api/users/a56434345fa23434fd
{name: 'chidume', age: 27, email: 'kurtwanger5@gmail.com', _id: 'a56434345fa23434fd'}
This returns the user with the name “chidume”:
$ curl localhost:3000/api/users/5fa23434fda5643434
{name: 'nnamdi', age: 20, email: 'kurtwanger40@gmail.com', _id: '5fa23434fda5643434'}
This returns the user with the name “nnamdi”:
Lastly, let’s edit user “5fa23434fda5643434”:
$ curl localhost:3000/api/users/5fa23434fda5643434 -X PUT --data{"'name': 'nnam', 'age': 29, 'email': 'kurtwnager@gmail.com'"}
{name: 'nnam', age: 29, email: 'kurtwanger@gmail.com', _id: '5fa23434fda5643434'}
```sh
See, it returns the user with the updated values.
We can test the delete by deleting the user 5fa23434fda5643434.
```sh
$ curl localhost:3000/api/users/5fa23434fda5643434 -X DELETE
USER DELETED
We learned a great deal in this post. First, we learned about the new Node.js web framework Fastify, how we can create fast API endpoints with it, and how to use the MongoDB database. We learned about its Express.js and Hapi.js-like routing system.
Fastify is not only limited to REST; we can use it with GraphlQL, gRPC, etc. It is also not only limited to MongoDB, and can be used with other databases (SQL or NoSQL). The sky is the limit.
If you have any questions regarding this or anything to add, correct, or remove, please comment, email, or DM me.
Thanks!
Free Resources