Introduction
If you're coming from Frontend Web, iOS, or Android app development your knowledge of backend development might only be limited on how to query an API endpoint and magically receive the exact data you need. If you're exclusively a client (web, iOS, Android) developer then you might find that you don't really need to fully understand how the backend is implemented but it might help you to understand at a high level what happens when you dispatch a request to a backend API. That's the purpose of this article. We are going to be using nodejs, expressjs, and micronode-cli to build a simple REST API.
Prerequisites
In order to successfully complete this lesson you need to have the following software downloaded an installed on your computer:
- nodejs - Runtime Environment to run Javascript applications outside of your browser
- Any code editor. We will be using VSCode in this lesson but any editor is fine.
- Postman - To send requests to our API without needing a browser.
Step One: Installation
Typically, when building a web server in nodejs, you'd need to manually install your dependencies which, at a minimum, usually consists of a Web Framework. A web framework provides some higher-level abstractions on top of some web protocol to make development of web services easy and seamless. While you can create a web server without using a framework, using battle tested software and web frameworks ensures that you are on the right track to building resilient and performant backend services. In this lesson, we are going to be using expressjs which is a web framework that makes it extremely simple to develop REST (REpresentational State Transfer) APIs. The standard steps to create a backend API would be as follows:
$ npm init // initialize your Node JS project
$ npm install express // install the express js framework as a dependency
$ touch server.js // create a server.js file
When creating multiple services, these steps can seem repetitive which is a perfect example of something that can be automated. The micronode-cli
application automates this process. We can install and run this script with the following commands:
$ npm install -g micronode-cli // installs micronode-cli globally on your system
Now, in the directory of your choice, you can initialize (create) your service with the following commands:
$ mn create-service first-service
You'll see the following output:
Creating service named: first-service
Directory: [PATH]
Running npm init...
Node Project Initialized
Installing Dependencies...
Dependencies Installed:
- express
Creating Dockerfile...
Initializing API...
Creating index.js ...
Dockerfile created.
API Created.
Server definition successfully created.
Both mn
and micronode
are valid versions of the command. In this case, we are using the shorthand mn
. create-service
is the action we want to run with the mn
cli tool. The create-service
command takes two arguments:
- a service name (
first-service
in this case) and - An optional path to where to locate the service. The default is the current directory if left blank.
For reference, these are the arguments that are expected by create-service
$ mn create-service [SERVICE_NAME] -d [LOCATION]
After running the above command, you should have the following directory structure where you chose to install.
first-service/
node_modules/
api/
api.js
index.js
Dockerfile
package.json
package-lock.json
Let's talk about each of these files in turn.
-
index.js
- This is where the definition of your service resides. By naming your server fileindex.js
you can simply start your server by running:$ node first-service
-
Dockerfile
- This is a file that will allow you to create a docker image of your service for easy deployment -
package.json
- This houses any configuration information related to your Node JS project. Each Node project has a package.json. -
api/api.js
- This file houses all of the REST API Routes that your service uses. We'll get more into this later. -
node_modules
- This is where all of your dependencies are installed. -
Step Two: Running Our Server
As stated above, we can run our server by running:
$ node first-service
You should see the following output
first-service Service is listening on Port: 8000
Our service is now ready to receive REST API requests on port 8000 on localhost (i.e your computer)
.
Step Three: Querying our Service
Now that we have our service running, we can start sending requests to it. But how do we know which types of requests this service accepts? To understand this, we need to inspect our index.js
and our api/api.js
files. Let's take a look at the api/api.js
first.
api.js
Your api.js
file should look something like this.
const express = require('express');
const router = express.Router();
// Define Routes Here:
// CREATE
router.post("/", (req, res) => {
const { body } = req;
res.status(200).send("Response from Post Request");
});
// READ
router.get("/:id", (req, res) => {
const { id } = req.params;
res.status(200).send("Getting Data for: " + id);
});
// UPDATE
router.put("/:id", (req, res) => {
const { id } = req.params;
res.status(200).send("Updating Data for: " + id);
});
// DELETE
router.delete("/:id", (req, res) => {
const { id } = req.params;
res.status(200).send("Deleting data for: " + id);
});
module.exports = { router };
The api.js
file simply defines a set of routes that are bound to certain request method types (i.e GET, POST, PUT, DELETE). Each expressjs request method accepts two parameters. The first parameter is a string denoting the path (url) that this particular method should be bound to. The second parameter is the function that should be executed when a request comes in. The function parameters are a req (Request)
object and a res (Response)
object. The response object is what we send back to a client that is querying our API. Let's take a closer look at the get request defined above.
router.get("/:id", (req, res) => {
const { id } = req.params;
res.status(200).send("Getting Data for: " + id);
});
- The
router.get("/:id", ...)
is saying "define a get route with the path "/" that expects a url parameter and name that parameter "id". You would query this url by saying "/10" and the ID in this case would be 10. -
(req, res)
are the anonymous function parameters. The expressjs documentation defines what is contained inside of the request and response objects. Generally speaking, the request object contains any information associated with a request (i.e parameters, query key-values, request body, headers, etc.) -
const { id } = req.params;
- This line is using destructuring which is javascript concept that allows us to pull out named object fields with the{}
syntax. The equivalent syntax without destucturing would beconst id = req.params.id;
. -
res.status(200).send("Getting Data for: " + id);
- We are using theresponse (res)
object to send a response back to the entity that sent a request to this endpoint. We do two things: 1. Set the response status code to OK (200), and we send some data. In this case it's a simple string, but in a real-world setting this would be some complex JSON data structure. Now let's look at ourindex.js
index.js
Your index.js
should look something like this.
const express = require("express");
const app = express(); // creates our application
app.use(express.json()); // parses the body of the requst into json
const { router } = require("./api/api"); // pull in the routes from our api.js file
const port = process.env.PORT || 8000; // pick a port to listen to.
const serviceName = "first-service"
app.use(`/api/${serviceName}`, router); // the path that the API in api.js should be responsible for.
// start our server
app.listen(port, async () => {
console.log(`${serviceName} Service is listening on Port: ${port}`);
});
The key difference with this file when compared to the api/api.js
file is that we are not defining our API routes in the server file. Instead, we have placed them in a separate file and imported (required) them in the server file. This is beneficial for maintainability and testing purposes and keeps our index.js
file lightweight and easy to understand. There are two important statements in this file:
-
app.use("/api/first-service", router)
- We have substituted the templated string for illustrative purposes but basically, our server will use the routes associated with the router (thinkapi/api.js
) only when a request comes in with the path beginning with/api/first-service
-
app.listen
is the final statement in this file and it tells our service to start up and listen to whatever port we have defined. In this case, it's8000
.
Now, to query our API, we can send a simple get request to the following route
using our web browser.
http://localhost:8000/api/first-service/10
And your response should be the following single line of text:
Getting Data for: 10
YAY! You've just built and queried your first API! Now, try to query some of the other endpoints using Postman!
Summary
In this lesson, we created a simple REST API with very little code by using the micronode-cli
. In reality, you would use micronode-cli
to build a simple scaffold of your backend service, and then implement the routes based on your particular use-case. nodejs
+ expressjs
make it extremely easy to build and test backend services and I would highly recommend getting familiar with the express documentation going forward.
Top comments (0)