For the next piece in my coding tutorial series, I’m going to build a backend server for a simple blogging tool using node.js, express.js, and MongoDB. This might seem a bit odd coming on the heels of my deep dive on Redux, but I’ve been heavily focusing on React and other frontend tools lately while neglecting backend code, so this will act as my refresher before I dive into more complex projects.
Since I find lengthy introductions boring and I don’t know what else to write here, let’s just dive right in! First thing’s first, you should install Node and MongoDB on your computer, along with MongoDB Compass and Postman. Setting those things up are beyond the scope of this article and you can find better tutorials elsewhere, so go do that if you haven’t already.
With that done, let’s begin putting the building blocks in place for our projects. First thing’s first, we need to run a few commands in the terminal:
// Terminal yarn add express npm init -y
We need to add the express npm package to our project which will create the server itself, and we need to initialize the package.json file, passing it the -y flag to provide the default blank answer to all the question it prompts us with. From here we can begin setting up our basic server, which we’ll do by creating an src folder in the root of the directory (which I’ve titled “simple_blog” in this instance), creating an app.js file in that folder, and adding the following code to app.js:
// src/app.js const express = require('express'); const app = express(); app.listen(3000, () => { console.log('Server running on port 3000') })
Also, we’re going to replace the test script in the package.json file with the following:
// package.json "scripts": { "start": "node app.js" },
With that in place, we can now run the server by typing “yarn start” in the console, and we should see the “Server running on port 3000” message printing to the console:
So far so good! With these basic building blocks in place, let’s move on to setting up our basic data structure. In this case we’re going to use MongoDB, so we’ll need to install the relevant packages:
// Terminal yarn add mongodb mongoose
And to add mongoose to the app.js file:
// src/app.js const express = require('express'); const mongoose = require('mongoose'); const app = express(); app.listen(3000, () => { console.log('Server running on port 3000') })
With that in place, we can now begin setting up our database, which we’ll do by adding the following to app.js before the app.listen call:
// src/app.js // Database mongoose.connect('mongodb://localhost:27017/simple-blog-exercise', { useNewUrlParser: true, useUnifiedTopology: true }) .then(() => console.log('Connected to the database')) .catch((err) => console.log(err))
Now that we’ve got that in place, if we kill the server and start it back up we should see the “Connected to the database” message printing to the console as well:
Which we do! Next we’re going to add some middleware that allows us to parse incoming data, into such a format that we can use it in our application. To do this, we’ll add the following to our app.js file before the app.listen call:
const bodyParser = require('body-parser'); // [...] // Middleware app.use(express.urlencoded({ extended: true })); app.use(express.json()); app.listen(3000, () => { console.log('Server running on port 3000') });
These two calls to app.use allow us to play with whatever data we receive back from the database in a friendly way. The “extended: true” option allows us to work with nested objects, as opposed to just single level JS objects.
And with that, our basic server and database structure is in place! Not too bad, right? Still, there’s a long way to go before our app is actually capable of doing anything. So next up, we’re going to build out the actual data models that will be the framework for the information we’ll store in the database.
Since a blog is based on articles which are created by individual users, we’re going to start by building out the user model.
User Model
First thing’s first, we’ll create a models folder within the src folder, then a user.js file within that folder:
In the user.js file we’re going to create our first schema, which is essentially like an outline that defines all the characteristics of what a user data object can have/needs to have. We’re also going to need another npm package, “validator”, to validate that the info the user provides us is indeed valid for the given information fields:
// Terminal yarn add validator // src/models/user.js const mongoose = require('mongoose'); const validator = require('validator'); const userSchema = new mongoose.Schema( { name: { type: String, trim: true }, email: { type: String, trim: true, required: true, unique: true, lowercase: true, validate (email) { if (!validator.isEmail(email)) { throw new Error("Email provided is invalid!") } } }, password: { type: String, trim: true, required: true, minlength: 8, validate (password) { if (password.toLowerCase().includes('password')) { throw new Error("Password cannot contain the word 'password'!") } } } }, { timestamps: true } ) const User = mongoose.model('User', userSchema); module.exports = User;
Many things are happening here, so I’m going to break it out into bullet points:
- We are creating a variable called userSchema, that is equal to a new mongoose.Schema object.
- We pass to the object different data fields that each User object can have, which are provided by the user upon creating their account or editing their account.
- The three data fields (name, email, and password) are all strings, and we add the option “trim: true” to trim all extra whitespace off the beginning and end of each field.
- The other options are fairly self explanatory. The validator package has a pre-built email validation function (validator.isEmail), which we use to throw an error if the user provides an invalid email. We use the build in validate function to throw an error if the user includes the word “password” in their password.
- We also pass in “timestamps: true” to automatically create “createdAt” and “updatedAt” fields for each user object we create, which will automatically create and update them accordingly.
- Finally, we create a mongoose model, passing in the model name (‘User’), and the userSchema we just created, assigning the output to the User variable, which we then export from the file.
With this basic user model in place, we can now move on to creating the controller functions which will actually allow us to Create, Read, Update, and Delete a user!
User Controller
Again, we’ll start by creating a controllers folder within the src folder, and creating a user.js file within said folder:
We’ll need to import the User model we just created into this file, and we can begin by building the create function which will allow us to create a new user, then exporting the function:
const User = require('../models/user'); module.exports = { create: async (req, res) =>{ const user = new User(req.body) try { await user.save() res.status(201).send({ user }) } catch (e) { res.status(400).send(e) } } }
In this file we use module.exports to export the userController as an object, with each of the controller functions representation a key-value pair within that object.
For our first function we have create which we write as an async function (since we need to wait for the database to actually save the data before proceeding to the next step). The function has a request we send to the database, and a response we receive back from the database.
We begin by creating a new instance of the User model, passing it the req.body (which will contain the data provided by the user for their name, email, and password), and assigning it to the user variable.
Then we run a try/catch block, attempting to save the newly created user to the database. If it’s successful we send a 201 HTTP status code indicating the save was successful, along with the user object back to the browser. If it’s uncessful we send a 400 status code indicating that it was unsuccessful, along with the error message back to the browser.
Now that that’s done we can move on to the next step and a create a router which will give us an actual URL to send a request to, and if we’re successful we’ll create a new instance of the User object!
User Router
Again, we create the relevant folder and file here:
And add our code into the routers/user.js file:
// src/routers/user.js const express = require('express'); const router = new express.Router(); const userController = require('../controllers/user'); router.post('/users', userController.create); module.exports = router;
Here we again import the express package, then create a router and assign it to the router variable, import the create controller function we just wrote in the last file, and set up our route. This will need to be a post route as we’re posting information to the database, we set it up at the /users path, and pass in the controller function as the second argument.
With this done we should be ready to import it into app.js, and run a Postman request to create a new user! In app.js:
const userRouter = require('./routers/user'); // [...] // Routers app.user(userRouter); app.listen(3000, () => { console.log('Server running on port 3000') })
And that’s that! Now we can move over to Postman and test our work. First we’ll create a new collection and request in Postman:
And pass the required information (email, password) in as raw JSON data:
If we did this right (which I’m honestly not 100% sure on), when we click send we’ll receive the 201 http code confirming that our request was successful, and return the newly created User object:
And it worked! In the top right we can see the successful “201 Created” HTTP response, and in the body we have the User object that was created with all the relevant data.
And with that, I think we’ll wrap up part one of this series! We put a lot of the foundation in place for this app and were able to successfully send off our first request.
That said, there are still MANY issues with this app and many more things to add. The user’s password isn’t encrypted in any way before it’s saved to the database, and it’s returned as part of the HTTP request for the user to view which we definitely don’t want!
We also need to create routes and controller functions to read, update, and delete a User object, as well as store the user’s session in their browser so they don’t have to log back in every single time, and we still have to do all of these things for the actual articles as well!
So we’ll start diving deeper into all of that in the next part of this series. Til then!
-Brandon