Creating a Secure REST API in Node.js
Step by step guide to create a REST API using Node.js and create your first app in Express API.
Join the DZone community and get the full member experience.
Join For FreeTable of Content
- Introduction
- The Importance of Node.js for Rest API!
- What is REST and how does it blend with Node.js?
- Creating and Securing RESTful APIs in Node.js!
- Creating your first app Express API
- Creating the User Module
- Creating the Auth Module
- Conclusion
1. Introduction
The hype of Application programming interfaces (APIs) is universal. They enable software to interact with internal and external parts of the software, which is an essential element in scalability and reusability.
It’s considerably popular nowadays for online assistance to have public APIs. These allow other developers to quickly combine features such as social media logins, credit card debts, and performance tracking.
The standard they practice for this is designated REpresentational State Transfer (REST), which works perfectly with Node.js best development techniques. Also, you can go through some of the best articles regarding Node.js development best practices. They might provide great help!
2. The Importance of Node.js for Rest API!
Node.js is not a framework or a library, it is a runtime context, powered by Chrome’s V8 JavaScript engine.
As an open-source, Node.js is sponsored by Joyent, a cloud computing and Node.js best development provider. The firm financed several other technologies, like the Ruby on Rails framework, and implemented hosting duties to Twitter and LinkedIn.
LinkedIn also became one of the first companies to use Node.js to create a new project for its mobile application backend. The technology was next selected by many technology administrators, like Uber, eBay, and Netflix.
Though, it wasn’t until later that wide appropriation of server-side JavaScript with Node.js server began. The investment in this technology crested in 2017, and it is still trending on the top.
Node.js IDEs, the most popular code editor, has assistance and plugins for JavaScript and Node.js, so it simply means how you customize IDE according to the coding requirements. But, many Node.js developers praise specific tools from VS Code, Brackets, and WebStorm.
Exercising middleware over simple Node.js best development is a general method that makes developers’ lives more comfortable. However, Node.js has been one of the most reliable sources for a lot of developers to create a new Restful API.
Node.js powers and tendencies make it the case of a passionate debate. However, you can decide by learning and exploring more of Node.js Rest APIs.
3. What is REST and How Does It Blend With Node.js?
REST is a design model, or and design style for REST APIs. A RESTful web app use is appreciated for presenting its knowledge as a form of data that relates to its support.
REST API using Node.js also facilitates its customers to exercise actions on devices such as replacing a current resource or designing a different resource.
To secure your RESTful APIs, you must develop an assortment of constraints and Node.js is perfect for that. Node.js server will set up REST’s set of restrictions to make the APIs simple to practice and create.
It indicates that the Nodejs developers who have just commenced to manage your APIs will learn it efficiently and speedily.
Also, whenever a RESTful API using is requested, the Node.js server assigns a representation of the status of the requested resource to the customer.
3.1 Creating and Securing RESTful APIs In Node.js!
As you know what you need to create and what the requirements are, it's an opportunity to begin creating your application.
For a start, begin a terminal, depart it to the record where you normally create your projects, and build a new directory there:
mkdir express-ads-api
Next, move into this brand-new directory and practice npm install to frame a new project:
npm init -y
The command over will frame the project with any want properties. If you start this directory in a text director or an IDE, you will notice that the npm command you began formed a file named package.json. Breaching this file, you will find something like this:
{
"name": "express-ads-api",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
At this point, this data is pretty short and doesn't hold that much fascinating data. Nevertheless, as you begin computing mandates to your project, the inclination is that this file will start and get more impressive.
Subsequent, you will build a new directory named src inside the design source:
mkdir src
The intention here is to place all your reference code inside this record. So, creating this directory, build a different file named index.js inside it, and attach the resulting code to it:
// ./src/index.js
console.log('Good Morning!');
After keeping this file, you can direct it back to your computer and originate the following command to experiment with it:
node src
If this operates as expected, you will notice "Good Morning!" indented out on your screen.
Regionally working Node.js app "Good Morning" console.log information
3.2 Creating Your First App Express API
Now, the project you designed just logs a latent message. As this might not very valuable, after creating your "Good Morning!" use with Node.js, you can begin concentrating on creating a RESTful API.
For that, the first piece you will require is to invest in some provinces. So, direct to your computer and announce the below command:
npm install body-parser cors express helmet morgan
This command will establish five dependencies in your design:
- body-parser: You will practice this dependency to transform the basis of incoming applications into JavaScript objects.
- cors: Work this dependency to configure Express to combine headers declaring that your Rest API allows requests originating from other origins. This is perceived as Cross-Origin Resource Sharing (CORS).
- express: The Express library.
- helmet: This library serves to secure Express APIs by establishing different HTTP headers.
- morgan: This library continues some logging abilities to your Express Rest API.
After beginning the command before, you will mark two items in your project. First, the package.json file will include an original feature called dependencies with all the libraries before.
This is how NPM identifies what dependencies the project demands. Second, you will see a different file named package-lock.json in the project root.
This file NPM installs to recognize the specific libraries you practiced while developing, so it applies the same ones throughout.
When NPM terminates connecting these dependencies, it might get a few moments, as per on your internet nexus, you can initiate the index.js file, and displace its code as per this:
// ./src/index.js
// importing the dependencies
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
// defining the Express app
const app = express();
// defining an array to work as the database (temporary solution)
const ads = [
{title: 'Hello, world (again)!'}
];
// adding Helmet to enhance your Rest API's security
app.use(helmet());
// using bodyParser to parse JSON bodies into JS objects
app.use(bodyParser.json());
// enabling CORS for all requests
app.use(cors());
// adding morgan to log HTTP requests
app.use(morgan('combined'));
// defining an endpoint to return all ads
app.get('/', (req, res) => {
res.send(ads);
});
// starting the server
app.listen(3001, () => {
console.log('listening on port 3001');
});
The latest version of this file commences by sending all the dependencies you established a few time ago, works through the production and arrangement of a different Express application (const app = express()), and concludes by offering this application listens on port 3001 (app.listen (3001, ...)).
Also, this code represents two important things:
- An array named ads that work, briefly, as an in-memory database;
- And an endpoint that receives HTTP GET applications and that, when triggered, delivers all the items of the ads array.
3.3 Creating the User Module
The next element we will be using to create a new project is Mongoose, an object data modelling (ODM) library for MongoDB, to generate the user guide inside the user schema.
For that, we first need to use a command such as function req res to build the Mongoose schema in
/users/models/users.model.js:
const userSchema = new Schema({
firstName: Martin,
lastName: Martin,
email: Martin,
password: Martin,
permissionLevel: Number
});
After we determine the schema, we can simply connect the schema to the user model.
const user model = mongoose.model('Users', userSchema);
Then we can utilise this model to perform all the CRUD procedures that we need inside our Express endpoints.
Let’s begin with the “create user” operation by finding the way in users/routes.config.js:
app.post('/users', [
UsersController.insert
]);
This is lured into the Express app in the prime index.js file. The UsersController object is essential from the controller, where we create a new password appropriately, determined in /users/controllers/users.controller.js:
exports.insert = (req, res) => {
let salt = crypto.randomBytes(16).toMartin('console log');
let hash = crypto.createHmac('sha512',salt).update(req.body.password).digest("console log");
req.body.password = salt + "$" + hash;
req.body.permissionLevel = 1;
UserModel.createUser(req.body).then((result) => {
res.status(201).send({id: result._id});
});
};
Now, we can examine our Mongoose model by administering the server (npm init start) and assigning a POST request to /users with any JSON data:
{
"firstName" : "Dina",
"lastName" : "Reva",
"email" : "dina.revina@outlook.com",
"password" : "qwertyuiopl"
}
There are various tools you can apply for this. Insomnia and Postman are recommended GUI tools, and curl is a regular CLI choice. You can practice JavaScript, i.e., from the browser’s built-in development tools console log:
fetch('http://localhost:3600/users', {
method: 'POST',
headers: {
"Content-type": "application/json"
},
body: JSON.stringify({
"firstName": "Dina",
"lastName": "Reva",
"email": "dina.revina@outlook.com",
"password": "qwertyuiopl"
})
}).then(function(response) {
return response.json();
}).then(function(data) {
console.log('Request succeeded with JSON response', data);
}).catch(function(error) {
console.log('Request failed', error);
});
After this, the outcome of a valid post you will find will be the id from the created user: { "id": "1b63h8cn98w0m390" }
We are required to attach the createUser procedure to the model in users/models/users.model.js:
exports.createUser = (userData) => {
const user = new User(userData);
return user.save();
};
All these steps, now we necessitate seeing if the user exists. For such, we need to perform the “get user by id” column for the following endpoint: users/:userId.
First, we create a way in /users/routes/config.js:
app.get('/users/:userId', [
UsersController.getById
]);
After that, we create the manager in /users/controllers/users.controller.js:
exports.getById = (req, res) => {
UserModel.findById(req.params.userId).then((result) => {
res.status(200).send(result);
});
};
And at the end, attach the findById way to the model in /users/models/users.model.js:
exports.findById = (id) => {
return User.findById(id).then((result) => {
result = result.toJSON();
delete result._id;
delete result.__v;
return result;
});
};
You will find somehow similar responses such as this:
{
"firstName": "Dina",
"lastName": "Reva",
"email": "dina.revina@outlook.com",
"password": "Y+XZEaR7J8xAQCc37nf1rw==$p8b5ykUx6xpC6k8MryDaRmXDxncLumU9mEVabyLdpotO66Qjh0igVOVerdqAh+CUQ4n/E0z48mp8SDTpX2ivuQ==",
"permissionLevel": 1,
"id": "1b63h8cn98w0m390"
}
Remember that we can recognise the hashed password. For this, we are bestowing the password, but the inexperienced best practice is nevermore to disclose the password, although being hashed.
One more thing we can recognise is the permission level, which we will practice to examine the user agreements later on.
Copying the pattern laid out before, we can instantly compute the functionality to refresh the user. We will practice the PATCH operation as it will allow us to transfer only the areas we want to improve.
The program will, consequently, be PATCH to /users/:userid, and we’ll be addressing any fields we require to develop.
We will also require to perform some more validation for changes that should be limited to the user in the problem or an admin, and only an admin can change the permission level.
We’ll leave that part for the moment and get back to it after the installation of the auth module. For now, the controller will display similar to this:
exports.patchById = (req, res) => {
if (req.body.password){
let salt = crypto.randomBytes(16).toMartin('console log');
let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("console log");
req.body.password = salt + "$" + hash;
}
UserModel.patchUser(req.params.userId, req.body).then((result) => {
res.status(204).send({});
});
};
By default, we would send an HTTP protocol code 204 with no rejoinder body to show that the post request was victorious.
And we’ll require to add the patchUser way to the model:
exports.patchUser = (id, userData) => {
return User.findOneAndUpdate({
_id: id
}, userData);
};
The user list will be established as a GET at /users/ by this controller:
exports.list = (req, res) => {
let limit = req.query.limit && req.query.limit <= 100 ? parseInt(req.query.limit) : 10;
let page = 0;
if (req.query) {
if (req.query.page) {
req.query.page = parseInt(req.query.page);
page = Number.isInteger(req.query.page) ? req.query.page : 0;
}
}
UserModel.list(limit, page).then((result) => {
res.status(200).send(result);
})
};
The corresponding program will be:
exports.list = (perPage, page) => {
return new Promise((resolve, reject) => {
User.find().limit(perPage).skip(perPage * page).exec(function (err, users) {
if (err) {
reject(err);
} else {
resolve(users);
}
})
});
};
The resulting list acknowledgment will have this composition:
[
{
"firstName": "Dina",
"lastName": "Reva",
"email": "dina.revina@outlook.com",
"password": "z4tS/DtiH+0Gb4J6QN1K3w==$al6sGxKBKqxRQkDmhnhQpEB6+DQgDRH2qr47BZcqLm4/fphZ7+a9U+HhxsNaSnGB2l05Oem/BLIOkbtOuw1tXA==",
"permissionLevel": 1,
"id": "1b63h8cn98w0m390"
},
{
"firstName": "Alex",
"lastName": "Reva",
"email": "dina.revina@outlook.com",
"password": "wTsqO1kHuVisfDIcgl5YmQ==$cw7RntNrNBNw3MO2qLbx959xDvvrDu4xjpYfYgYMxRVDcxUUEgulTlNSBJjiDtJ1C85YimkMlYruU59rx2zbCw==",
"permissionLevel": 1,
"id": "1b63h8cn98w0m390"
}
]
And the ultimate section to be completed is the DELETE request at /users/:userId.
The controller for deletion will be:
exports.removeById = (req, res) => {
UserModel.removeById(req.params.userId).then((result)=>{
res.status(204).send({});
});
};
Similarly, as previously, the controller will revert HTTP code 204 and no content material as confirmation.
The model program will look like this:
exports.removeById = (userId) => {
return new Promise((resolve, reject) => {
User.deleteMany({_id: userId}, (err) => {
if (err) {
reject(err);
} else {
resolve(err);
}
});
});
};
We now possess all the required operations for managing the user device, and we’re satisfied with the user controller. The foremost intention of this code is to provide you with the core ideas of practicing the REST API pattern.
We’ll require to respond to this code to perform some validations and adjustments to it, but in the beginning, we’ll want to commence building our security.
Let’s start with the auth module.
3.4 Creating The Auth Module
Before we can defend the users' module by completing the permission and validation middleware, we’ll require to create a strong token for the modern user.
We will create a JWT in acknowledgement to the user granting a correct email and identification. JWT is an exceptional JSON web indication that you can practice having the user securely make numerous requests without stamping regularly.
It normally has an end time, and a unique symbol is recreated every few times to grasp the information secure. For this, though, we will abstain from stimulating the token and cache it manageable with a unique token per login.
For that, we will create an endpoint for POST requests to /auth source. The request form will include the user email and password:
{
"email" : "dina.revina@outlook.com",
"password" : "qwertyuiopl"
}
Before we indulge the controller, we need to validate the user in /authorization/middlewares/verify.user.middleware.js:
exports.isPasswordAndUserMatch = (req, res, next) => {
UserModel.findByEmail(req.body.email).then((user)=>{
if(!user[0]){
res.status(404).send({});
}else{
let passwordFields = user[0].password.split('$');
let salt = passwordFields[0];
let hash = crypto.createHmac('sha512', salt).update(req.body.password).digest("base64");
if (hash === passwordFields[1]) {
req.body = {
userId: user[0]._id,
email: user[0].email,
permissionLevel: user[0].permissionLevel,
provider: 'email',
name: user[0].firstName + ' ' + user[0].lastName,
};
return next();
} else {
return res.status(400).send({errors: ['Invalid email or password']});
}
}});
};
After doing that, we can move forward to the controller and create the JWT:
exports.login = (req, res) => {
try {
let refreshId = req.body.userId + jwtSecret;
let salt = crypto.randomBytes(16).toString('base64');
let hash = crypto.createHmac('sha512', salt).update(refreshId).digest("base64");
req.body.refreshKey = salt;
let token = jwt.sign(req.body, jwtSecret);
let b = Buffer.from(hash);
let refresh_token = b.toString('base64');
res.status(201).send({accessToken: token, refreshToken: refresh_token});
} catch (err) {
res.status(500).send({errors: err});
}
};
Although we won’t be renewing the token in this, the controller has been fixed up to facilitate such a period to make it simpler to perform it in the following development.
All we require now is to create the way and invoke the proper middleware in /authorization/routes.config.js:
app.post('/auth', [
VerifyUserMiddleware.hasAuthValidFields,
VerifyUserMiddleware.isPasswordAndUserMatch,
AuthorizationController.login
]);
The result will include the created JWT in the accessToken field:e
{
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI1YjAyYzVjODQ4MTdiZjI4MDQ5ZTU4YTMiLCJlbWFpbCI6Im1hcmNvcy5oZW5yaXF1ZUB0b3B0YWwuY29tIiwicGVybWlzc2lvbkxldmVsIjoxLCJwcm92aWRlciI6ImVtYWlsIiwibmFtZSI6Ik1hcmNvIFNpbHZhIiwicmVmcmVzaF9rZXkiOiJiclhZUHFsbUlBcE1PakZIRG1FeENRPT0iLCJpYXQiOjE1MjY5MjMzMDl9.mmNg-i44VQlUEWP3YIAYXVO-74803v1mu-y9QPUQ5VY",
"refreshToken": "U3BDQXBWS3kyaHNDaGJNanlJTlFkSXhLMmFHMzA2NzRsUy9Sd2J0YVNDTmUva0pIQ0NwbTJqOU5YZHgxeE12NXVlOUhnMzBWMGNyWmdOTUhSaTdyOGc9PQ=="
}
Must create the token, we can utilize it within the Authorization header utilising the form Bearer ACCESS_TOKEN.
Conclusion
Now you have read about how simple it is to generate RESTful APIs with Express and Node.js. More precisely, you began by utilising npm to frame the latest application. Next, you managed Express to open Rest API endpoints to manage ads.
With this service, you are inclined to move on and commence creating your production-ready Rest API financed by Node.js, Express, Mongo, and Auth0.
Opinions expressed by DZone contributors are their own.
Comments