Firstly, create a file named server.js that will include two middleware functions. One that configures a session and the other that makes sure that there is a connection to the MongoDB before allowing any route to be called. Then, we mount our API routes to a specific path:
- Create a new file named server.js
- Include the required libraries. Then, initialize a new ExpressJS application and create a connection to MongoDB:
const mongoose = require('mongoose') const express = require('express') const session = require('express-session') const bodyParser = require('body-parser') const MongoStore = require('connect-mongo')(session) const api = require('./api/controller') const app = express() const db = mongoose.connect( 'mongodb://localhost:27017/test' ).then(conn => conn).catch(console.error)
- Use the body-parser middleware to parse the request body as JSON:
app.use(bodyParser.json())
- Define an ExpressJS middleware function that will ensure your web application is connected to MongoDB first before allowing next route handlers to be executed:
app.use((request, response, next) => {
Promise.resolve(db).then(
(connection, err) => (
typeof connection !== 'undefined'
? next()
: next(new Error('MongoError'))
)
)
})
- Configure express-session middleware to store sessions in the Mongo database instead of storing in memory:
app.use(session({ secret: 'MERN Cookbook Secrets', resave: false, saveUninitialized: true, store: new MongoStore({ collection: 'sessions', mongooseConnection: mongoose.connection, }), }))
- Mount the API controller to the "/api" route:
app.use('/users', api)
- Listen on port 1773 for new connections:
app.listen( 1337, () => console.log('Web Server running on port 1337'), )
- Save the file
Then, create a new directory named api. Next, create the model or business logic of your application. Define a schema for users with static and instance methods that will allow a user to signup, login, logout, get profile data, change their password, and remove their profile:
- Create a new file named model.js in the api directory
- Include the Mongoose NPM module and also the crypto NodeJS module that will be used to generate a hash for the user passwords:
const { connection, Schema } = require('mongoose') const crypto = require('crypto')
- Define the schema:
const UserSchema = new Schema({ username: { type: String, minlength: 4, maxlength: 20, required: [true, 'username field is required.'], validate: { validator: function (value) { return /^[a-zA-Z]+$/.test(value) }, message: '{VALUE} is not a valid username.', }, }, password: String, })
- Define a static model method for login:
UserSchema.static('login', async function(usr, pwd) { const hash = crypto.createHash('sha256') .update(String(pwd)) const user = await this.findOne() .where('username').equals(usr) .where('password').equals(hash.digest('hex')) if (!user) throw new Error('Incorrect credentials.') delete user.password return user })
- Define a static model method for signup:
UserSchema.static('signup', async function(usr, pwd) { if (pwd.length < 6) { throw new Error('Pwd must have more than 6 chars') } const hash = crypto.createHash('sha256').update(pwd) const exist = await this.findOne() .where('username') .equals(usr) if (exist) throw new Error('Username already exists.') const user = this.create({ username: usr, password: hash.digest('hex'), }) return user })
- Define a document instance method for changePass:
UserSchema.method('changePass', async function(pwd) { if (pwd.length < 6) { throw new Error('Pwd must have more than 6 chars') } const hash = crypto.createHash('sha256').update(pwd) this.password = hash.digest('hex') return this.save() })
- Compile the Mongoose schema into a model and export it:
module.exports = connection.model('User', UserSchema)
- Save the file
Finally, define a controller that will transform the request body to actions that our model can understand. Then export it as an ExpressJS router that contains all API paths:
- Create a new file named controller.js in the api folder
- Import model.js and initialize a new ExpressJS Route:
const express = require('express') const User = require('./model') const api = express.Router()
- Define a request handler to check if a user is logged in and another request handler to check if the user is not logged in:
const isLogged = ({ session }, res, next) => { if (!session.user) res.status(403).json({ status: 'You are not logged in!', }) else next() } const isNotLogged = ({ session }, res, next) => { if (session.user) res.status(403).json({ status: 'You are logged in already!', }) else next() }
- Define a post request method to handle requests to "/login" endpoint:
api.post('/login', isNotLogged, async (req, res) => { try { const { session, body } = req const { username, password } = body const user = await User.login(username, password) session.user = { _id: user._id, username: user.username, } session.save(() => { res.status(200).json({ status: 'Welcome!'}) }) } catch (error) { res.status(403).json({ error: error.message }) } })
- Define a post request method to handle requests to "/logout" endpoint:
api.post('/logout', isLogged, (req, res) => { req.session.destroy() res.status(200).send({ status: 'Bye bye!' }) })
- Define a post request method to handle requests to "/signup" endpoint:
api.post('/signup', async (req, res) => { try { const { session, body } = req const { username, password } = body const user = await User.signup(username, password) res.status(201).json({ status: 'Created!'}) } catch (error) { res.status(403).json({ error: error.message }) } })
- Define a get request method to handle requests to "/profile" endpoint:
api.get('/profile', isLogged, (req, res) => { const { user } = req.session res.status(200).json({ user }) })
- Define a put request method to handle requests to "/changepass" endpoint:
api.put('/changepass', isLogged, async (req, res) => { try { const { session, body } = req const { password } = body const { _id } = session.user const user = await User.findOne({ _id }) if (user) { await user.changePass(password) res.status(200).json({ status: 'Pwd changed' }) } else { res.status(403).json({ status: user }) } } catch (error) { res.status(403).json({ error: error.message }) } })
- Define a delete request method to handle requests to "/delete" endpoint:
api.delete('/delete', isLogged, async (req, res) => { try { const { _id } = req.session.user const user = await User.findOne({ _id }) await user.remove() req.session.destroy((err) => { if (err) throw new Error(err) res.status(200).json({ status: 'Deleted!'}) }) } catch (error) { res.status(403).json({ error: error.message }) } })
- Export the route:
module.exports = api
- Save the file