Testing NodeJS APIs pt. II: some authenticated endpoints to test in the future

Christian Benseler
3 min readDec 2, 2019

--

In the second post about testing endpoints from an NodeJS app with Express, I will show how to create basic endpoints for a model (in this example, a blog Post that as a relation with an user) that require authentication — so in the third post we will finally see how to test them! In the first, I have shown how to create an the server with user authentication.

Let’s create a Post model, that will reference the authenticated user. In models/ folder, create Post.js with

const mongoose = require('mongoose')
const postSchema = new mongoose.Schema({
title: { type: String, required: true },
content: { type: String },
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
}, { timestamps: true })
module.exports = mongoose.model("post", postSchema)

In the app.js, we can handle the authentication logic. First, we will check in every request if there is a bearer token:

app.use( async (req, res, next) => {
if(!req.headers['authorization'])
return next()
const bearer = req.headers['authorization'].split('Bearer ')[1]
try {
const decoded = await jwt.verify(bearer, JWTSECRET)
const user = await User.findOne({ _id: decoded.id }, { password: 0 } )
if(user)
req.user = user
next()
} catch(e) {
next()
}
})

If token is present, we verify if it is valid (example: it can't be an expired) and then add the user to the request.

Now we can create a function that will handle authentication in some endpoints:

const isAuthenticated = (req, res, next) => {
if(!req.user)
res.status(401).json({ message: 'User must be authenticated' })
next()
}

If there is no user in the request, the server throws a response with status 401 and a friendly message.

Now we can create endpoints (following REST pattern) to manage the Post model (create/edit, show, list, delete) with a reference to the authenticated user, that is the "owner" of each post.

The most important part is this:

app.post('/posts', isAuthenticated, fn)

The endpoints that require a user use the isAuthenticated middleware.
The source from the endpoints is below:

const Post = require('./models/Post')//REST Post modelapp.get('/posts/:id', async (req, res, next) => { 
try {
const post = await Post.findOne({ _id: req.params.id })
if(!post)
return res.status(404).json({ message: 'Post not found' })
res.json(post)
} catch(e) {
next(e)
}
})
app.get('/posts', async (req, res, next) => {
try {
const posts = await Post.find()
res.json({ posts })
} catch(e) {
next(e)
}
})
app.post('/posts', isAuthenticated, async (req, res, next) => {
const { title, content } = req.body
const p = new Post({ title, content, user: req.user })
try {
await p.save()
res.json(p)
} catch(e) {
next(e)
}
})
app.put('/posts/:id', isAuthenticated, async (req, res, next) => {
const { title, content } = req.body
try {
const post = await Post.findOne({ user: req.user,_id: req.params.id })
if(!post)
return res.status(401).json({ message: 'User does not have permission to edit this post' })
post.title = title
post.content = content
await post.save()
res.json(post)
} catch(e) {
next(e)
}
})
app.delete('/posts/:id', isAuthenticated, async (req, res, next) => {
try {
const deletion = await Post.deleteOne({ user: req.user, _id: req.params.id })
if(deletion.deletedCount === 0)
return res.status(400).json({ status: 'NOK', message: 'Could not delete the post'})
res.json({ status: ‘OK’, message: 'Post was deleted'})
} catch(e) {
next(e)
}
})
//Nested posts — from userapp.get(‘/users/:user_id/posts’, async (req, res, next) => {
try {
const posts = await Post.find({ user: req.params.user_id })
res.json({ posts })
} catch(e) {
next(e)
}
})//user
app.get('/profile', isAuthenticated, async (req, res, next) => {
try {
res.json({ profile: { email: req.user.email } })
} catch(e) {
next(e)
}
})

I have created also two extra endpoints: one that will show the posts from a particular user and another one for the user "profile".

Now we have a more realistic NodeJS API that we will write tests, in the next post!

The full and updated source from the project is in Github and the exact version from this Medium post is here.

--

--