Testing NodeJS APIs pt. I: create Express API with authentication

Christian Benseler
3 min readNov 29, 2019

--

I want to show how I'm testing NodeJS APIs with Jest and Supertest in a series of posts. First of all, in order to test an API, we must to have an API.
So in the part one, I will show how to create a simple API, using Express and MongoDB, that handles user authentication (sign up and in).

Creating a basic Express server

mkdir nodejs-example
cd nodejs-example
npm init
npm i — save mongodb mongoose express
touch app.js

Edit app.js

const express = require('express')
const app = express()
app.set('host', process.env.EXPRESS_HOST || '0.0.0.0')
app.set('port', process.env.EXPRESS_PORT || 8080)
if(process.env.NODE_ENV !== 'test') {
app.listen(app.get('port'), () => {
console.log('App is running at http://localhost:%d in %s mode', app.get('port'), app.get('env'))
console.log('Press CTRL-C to stop\n')
})
}
module.exports = app

Run it

node app.js

It will output

App is running at http://localhost:8080 in development mode
Press CTRL-C to stop

Connect to a MongoDB instance (inside app.js)

const mongodbUri = process.env.MONGOURL || 'mongodb://localhost:27017/nodejs_jestsupertestmongodb_example'mongoose.connect(mongodbUri, { useNewUrlParser: true })
mongoose.connection.on('connected', () => {
console.log('MongoDB connection established successfully')
})
mongoose.connection.on('error', (err) => {
console.error('%s MongoDB connection error. Please make sure MongoDB is running.')
process.exit()
})

Running again the app.js,it will output too

MongoDB connection established successfully

That shows the app is connected to a MongoDB instance.

Create a schema for a user; in models/ folder, create a User.js

const mongoose = require('mongoose')
const bcrypt = require('bcryptjs')
const userSchema = new mongoose.Schema({
email: { type: String, unique: true },
password: String
}, { timestamps: true })
userSchema.pre('save', function (next) {
let user = this
if (!user.isModified('password')) { return next() }
bcrypt.hash(user.password, 10).then((hashedPassword) => {
user.password = hashedPassword
next()
})
}, function (err) {
next(err)
})
userSchema.methods.comparePassword = function (candidatePassword, next) {
bcrypt.compare(candidatePassword, this.password, function (err, isMatch) {
if (err) return next(err)
next(null, isMatch)
})
}
module.exports = mongoose.model("user", userSchema)

Install more dependencies

npm i --save bcrypt bcryptjs body-parser

Let's create the sign up endpoint. In app.js, add this code

//middleware that allows to easily parse he body from requests  
const bodyParser = require('body-parser')
app.use(bodyParser.json())
app.use(bodyParser.urlencoded())
//...
const User = require('./models/User')
app.post('/users/signup', async (req, res, next) => {
const { email, password } = req.body
if(password !== req.body.confirmPassword)
res.status(403).json({status: 'NOK' })
const u = new User({ email, password })
try {
await u.save()
res.json({status: "ok" })
} catch(e) {
next(e)
}
})

What does this endpoint? It receives via POST (this is the proper http verb to create a new entity). First, it tries to match password and confirmPassword parameters. If it doesn't, the server responds with a 403 status.
Otherwise, we can create a new user with its e-mail and password.

Then we can write the endpoint that will authenticate the user!

const jwt = require('jsonwebtoken')const JWTSECRET  = process.env.EXPRESS_JWTSECRET || 'somerandomstring'app.post('/users/signin', async (req, res, next) => {
const user = await User.findOne({ email: req.body.email })
if (!user) {
res.status(401).json({status: "NOK" })
}
user.comparePassword(req.body.password, (err, isMatch) => { if (isMatch) {
const exp = Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 7)
const token = jwt.sign({ id: user.id, exp }, JWTSECRET)
const { id, name } = user
res.status(200).json({
id, token
})
} else {
res.status(401).json({status: "NOK" })
}
})
})

Let's explain: first we import the lib that will help us to use json webtokens and then define a secret that will be used to generate each.
The endpoint is quite simple: it expects a POST with email and password attributes.

First it queries the database to find a user with the e-mail sent as parameter, then it use the UserSchema's comparePassword to match the password send with the one stored in the database (that is encrypted).

In case of success, we sign an json webtoken using the user's id, an expiration date (it is a timestamp, we calculated in the example one that will expire in 7 days) and then returns a json with the id and the token.

Next: the part II will cover how to create more endpoints for our API, with authentication control, so in the future we will be able to write more complex (and closer to real world scenarios).

The source code from the full project is in the Github and this tag has the exact version from this example. Inside the project there is Postman’s .json with the endpoints.

--

--