Table of contents
When you're working on the front-end, and your goal is to learn a new framework or technology, you start a new project to play with it. (at least I do so). But when you start thinking about the beck-end that you have to develop to work with the real data, it starts to be annoying as you only want to learn a front-end framework or some technology at this point. The answer is a mock server which you can set up to only care about the front-end. I was doing research and found this perfect npm library that fully covers my needs. You probably heard about it. It's name json-server.
But there is one thing that doesn't come out of the box. And that's authentication. In this article, we will set up the mock server with JWT token authentication. We will also create a to-do item to demonstrate that authentication works as it should.
Setup base node project
Create a project folder:
mkdir mock-api-with-auth cd mock-api-with-auth
Init npm:
npm init --yes
Install required dependencies:
npm install bcrypt json-server jsonwebtoken
bcrypt - Will help us to store a password safelyjson-server - The mock serverjsonwebtoken - JWT token for the authentication
Create token storage and database
First, let's create a database. As we're using a mock server for the sake of simplicity, we need to create a simple JSON file describing our database.
Create a db.json, with the following content there:
{
"users": [],
"todos": []
}
In our database, we have two entities. The first one, which is obvious, is
Next, we need to create token storage to keep the JWT tokens.
Create auth.json, with the following content:
[]
Setup server
To keep it simple, I'll keep everything in a server.js file so you can copy/paste it in one go once you need it.
const jsonServer = require('json-server');
const fs = require('fs');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const fsPromises = fs.promises;
const server = jsonServer.create();
const router = jsonServer.router('db.json');
const middlewares = jsonServer.defaults();
const readJson = async (file) => {
const rawData = await fsPromises.readFile(file);
return JSON.parse(rawData);
}
const writeJson = async (content, file) =>
await fsPromises.writeFile(file, content);
const getDb = async() => readJson('db.json');
const getUsers = async() => (await getDb()).users;
const getTokens = async() => readJson('auth.json');
const writeTokens = async(content) => writeJson(JSON.stringify(content), 'auth.json');
const findUserByUsername = (usernameToFind, users = []) =>
users.find(({username}) => username === usernameToFind);
server.use(middlewares);
server.use(jsonServer.bodyParser);
server.use(async(req, res, next) => {
if(req.url === '/users' && req.method === 'POST') {
if (!req.body.username || !req.body.password) res.sendStatus(400);
req.body.password = await bcrypt.hash(req.body.password, 10);
next();
} else if(req.url === '/sign-in' && req.method === 'POST') {
if (!req.body.username || !req.body.password) return res.sendStatus(400);
const users = await getUsers();
const user = findUserByUsername(req.body.username, users);
if (!user) return res.sendStatus(403);
const isPasswordCorrect = await bcrypt.compare(req.body.password, user.password);
if(!isPasswordCorrect) return res.sendStatus(400);
const token = jwt.sign( { id: user.id, username: user.username },
'YOUR_SECRET', { expiresIn: '30d' });
const tokens = await getTokens();
const newTokens = [ ...tokens, token];
await writeTokens(newTokens);
return res.json({ ...user, token })
} else {
const authHeader = req.headers['authorization'];
if (!authHeader) return res.sendStatus(401);
const token = authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
const tokens = await getTokens();
if (!tokens.includes(token)) return res.sendStatus(401);
next();
}
});
server.use(router)
server.listen(3000, () => {
console.log('JSON Server is running')
});
The
Let's go over the most important parts to pay attention to:
// ...
if(req.url === '/users' && req.method === 'POST') {
if (!req.body.username || !req.body.password) res.sendStatus(400);
req.body.password = await bcrypt.hash(req.body.password, 10);
next();
// ...
Here we check if we have all the necessary data to register the user. Then we bcrypt a user password.
// ...
} else if(req.url === '/sign-in' && req.method === 'POST') {
if (!req.body.username || !req.body.password) return res.sendStatus(400);
const users = await getUsers();
const user = findUserByUsername(req.body.username, users);
if (!user) return res.sendStatus(403);
const isPasswordCorrect = await bcrypt.compare(req.body.password, user.password);
if(!isPasswordCorrect) return res.sendStatus(400);
const token = jwt.sign( { id: user.id, username: user.username },
'YOUR_SECRET', { expiresIn: '30d' });
const tokens = await getTokens();
const newTokens = [ ...tokens, token];
await writeTokens(newTokens);
return res.json({ ...user, token })
// ...
This part is responsible for user sign-in functionality. First, we check whether we have all the necessary to log in. Then we check if the password is correct. In case it is, we create a JWT token, store it and pass it to the user so that he can use it to work further with the information that is stored in the database.
The user must provide a bearer token to work with the API. To do this, he has to add to the request
// ...
} else {
const authHeader = req.headers['authorization'];
if (!authHeader) return res.sendStatus(401);
const token = authHeader.split(' ')[1];
if (!token) return res.sendStatus(401);
const tokens = await getTokens();
if (!tokens.includes(token)) return res.sendStatus(401);
next();
// ...
In the last part of our auth middleware (in the else block), we are in the part where a user wants to receive data from the database. So we're checking whether the user has a token and if it's correct. If it is, we let him get the data from the database. Otherwise, we return 401.
To run the server run this command:
node server.js
or add a script to package.json:
...
"start": "node server.js"
...
and run:
npm start
Testing API
Let's register a user first:
We received a user with an id.
Remember that you have to add a
Now, let's try to sign in:
The request was successful, and we received a JWT token. We'll use this token to create and get todos, so store it somewhere.
We're ready to create
Don't forget to add a bearer token to the headers.
In addition, we can get all todos using a GET method:
If we remove bearer token, we'll get unauthorized:
It looks like everything works as it should. This code can be reused as a base project for your future front-end learnings.
Sources and scripts
You can find a github repository with finished code here.
I also included a folder with bash scripts that can be used instead of the Postman app in this repository. These can help you populate a database with multiple users and todos from your terminal effortlessly.
scripts/sign-up.sh - Create userscripts/sign-in.sh - Log inscripts/create-todo.sh - Create todo