Interested in Express? Unsure how to get started with the MERN (MongoDB, Express.js, React.js, Node.js) ? This tutorial will get you up and running with a full CRUD (Create, Read, Update, DELETE) REST (Representational State Transfer) API in FIVE minutes! 🐼
Getting Started
This tutorial assumes you have nodejs
and npm
installed and configured, along with MongoDB. If not, leave a comment and I'll personally forward you my follow up post on Installing and Running Node.js
MVC 💎
Our application is going to follow the MVC (Model, View, Controller) design pattern
I will talk about design patterns In a later post but, for now, all you need to know is that the MVC pattern is divided In to three sections 📝 :
- Model - Model represents an object carrying data. It can also have logic to update the controller if its data changes.
- View - View represents the visualization of the data that model contains.
- Controller - Controller acts on both model and view. It controls the data flow into model object and updates the view whenever data changes. It keeps view and model separate.
The Beginning ↙️
To begin, let's create a new directory to work from:
- Navigate to your desired directory
- Run
mkdir my-express-app
(to create your project folder) - Run
npm init -y
(To initialize your node project) - Create the following folders/files (or copy and paste the commands provided!)
mkdir -p ./server/models; mkdir ./server/controllers; mkdir ./server/routes
- Then
touch ./server/index.js; touch ./server/models/user.model.js; touch ./server/controllers/user.controller.js; touch ./server/routes/user.routes.js
Now your project should look like this! 👀
Creating the Server! 🌀
The server needs three things to get running:
- Create a server application
- Consume route middleware
- Listen for requests
Let's break this down part by part
Create a server application
To begin with, we will need to install a few dependencies.
Open your terminal in the root of your project and run the following command:
npm install express mongoose
This installs two package dependencies. What are package dependencies? They are packages are APIs that can be used by your application to write code with. Your project then depends on this package to work (if you use the package).
Express
is the API we are going to use to create our server, routes and controllers.
mongoose
is an API that functions similarly to an ORM (Object Relational-mapper) that we are going to use to create our database model.
Open your ./server/index.js
file and paste the following code
const express = require('express');
const app = express();
app.use(express.json());
app.get('/', (req, res) => {
res.status(200).json({message: "Hello from my-express-app!"});
});
const PORT = 8080;
app.listen(PORT, () => {
console.log(`Server listening at http://localhost:${PORT}`);
});
Line by line:
const express = require('express');
Imports the Express API so that we can use it's features in our application.
const app = express();
Constructs a new Express application that functions as our server.
app.use(express.json());
Tells the Express application to use the JSON middleware (This is so we can see our request bodies with JSON)
app.get('/', (req, res) => {
res.status(200).json({message: "Hello from my-express-app!"});
});
Creates a GET
route and sends an initial response.
Lastly, In our package.json
file we need to add a script!
"server": "node server/index.js"
Running
npm run server
In a terminal at the root of the project, then navigating to http://localhost:8080/
should show us:
The Controllers! 🔧
As we are creating a CRUD
API, we need to create at least four controllers:
- create
- read
- update
- delete
Go ahead and add the following code to
./server/controllers/user.controller.js
```c
exports.createOneRequest = (req, res) => {
res.status(201).json({message: "New resource created!"});
}
exports.readOneRequest = (req, res) => {
res.status(302).json({message: "Resource found!"});
}
exports.updateOneRequest = (req, res) => {
res.status(301).json({message: "Resource updated!"});
}
exports.deleteOneRequest = (req, res) => {
res.status(202).json({message: "Resource deleted!"});
}
Each function is responsible for a corresponding HTTP request, and returns the following appropriate response status code, along with some JSON data to look at!
- 201 - Resource Created
- 302 - Resource Found
- 301 - Resource Moved Permanently
- 202 - Resource Accepted
These are the controllers that will handle our requests!
### **The Routes!** :bike:
Now we have our controllers, we need some routes for them to handle. We are going to use Express router to handle our CRUD routes!
In your `./server/routes/user.routes.js` add the following:
```c
const express = require('express');
const urlRoutes = express.Router();
const controller = require('../controllers/user.controller');
urlRoutes.post('/', controller.createOneRequest);
urlRoutes.get('/:id', controller.readOneRequest);
urlRoutes.put('/:id', controller.updateOneRequest);
urlRoutes.delete('/:id', controller.deleteOneRequest);
module.exports = urlRoutes;
Then add the following to your ./server/index.js
file:
const userRouter = require('./routes/user.routes');
...
app.use('/users', userRouter);
...
This mounts our newly created router to the /users
sub route. Meaning any path we specify must be prepended with /users
in order for the URL to be correct.
For example: http://localhost:8080/<SOME_OBJECT_ID>
would be an example of a URL that would not work given our current project structure.
http://localhost:8080/users/<SOME_OBJECT_ID>
Would be a correct URL as it has the /users
prepended!
Now, navigating to any of the URLs should return a response that looks something like this!
Build and integrate the Models! :octocat:
We're almost at the final section of this tutorial. If you made it this far, congratulations! you're one step away from an awesome REST API 😉
Add the following code to your ./server/models/user.model.js
file:
const mongoose = require('mongoose');
const UserModel = mongoose.model('User',
mongoose.Schema(
{
name: {
type: String
},
},
{timestamps: true}
)
);
module.exports = UserModel;
This creates a User Schema in your local MongoDB instance to be used.
Then, back in the ./server/controllers/user.controller.js
file:
Replace the contents of the createOneRequest request with:
exports.createOneRequest = async (req, res) => {
// req.body is for POST requests. Think 'body of the postman'
// destruct the name value from the request body
const {name} = req.body;
// check if database already contains this name
const foundUser = await UserModel.find({name});
// if no user is found, we can add this user to the database.
if(!foundUser || foundUser.length == 0) {
const user = new UserModel({name});
const response = await user.save();
res.status(201).json(response);
} else {
res.status(409).json({message: "User already exists!"});
}
}
This controller now handles three things!
- Check if a user already exists by the name provided.
- If no user exists, create one
- return response to client
Do the same for the readOneRequest
:
exports.readOneRequest = async (req, res) => {
// Best request is GET, we can get the ID from the request
// parameters.
const {id} = req.params;
// attempt to retrieve user
const foundUser = await UserModel.findOne({_id: id});
// return 404 if no user found, return user otherwise.
if(!foundUser || foundUser.length == 0) {
res.status(404).json({message: "User not found!"});
} else {
res.status(302).json(foundUser);
}
}
And for the putOneRequest
:
exports.updateOneRequest = async (req, res) => {
const {id} = req.body;
const foundUser = await UserModel.findOne({_id: id});
if(foundUser || foundUser.length == 0) {
const response = await foundUser.updateOne({_id: id});
res.status(301).json(response);
} else {
res.status(404).json({message: `User not found...`});
}
}
And lastly, the deleteOneRequest
:
exports.deleteOneRequest = async (req, res) => {
const {id} = req.params;
const foundUser = await UserModel.findOne({_id: id});
if(foundUser || foundUser.length == 0) {
const response = await foundUser.deleteOne({_id: id});
res.status(202).json(response);
} else {
res.status(404).json({message: `User not found...`});
}
}
Now that we have our CRUD operations built, all we need to do is configure the database and we are ready to go!
The Database connection! 📫
We need to open a connection to our Mongo Database so that our application can communicate with the database!
To do this, open your ./server/index.js
script and add the following code:
...
const mongoose = require('mongoose');
const db = mongoose.connect('mongodb://localhost:27017/db', {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true
}).then((response) => {
console.log('Connected to the database...');
return response;
});
...
Run npm run server
and sit back and relish in the brand new Full CRUD Rest API you have majestically built! 🙌
Summary 👋
There you have it! a fully functioning, full featured CRUD methods Restful API! We went through constructing an express application, consuming middleware and routes, route controllers and finally database models. A very bus tutorial! 😫
Feel free to leave comments below, any feedback is welcome! Link me to some of the apps you've built from this!
I hope you enjoyed this tutorial and feel like checking out my other social media! This is my first post on this site to please be kind 😄
Top comments (16)
I know it may sound irrelevant, but what is the benefit of [ -Virtualbox:] and how to apply it ?
Is it like virtual environment (venv) in python ?
Hey Bruno! Thats a great question!
I am running a virtual machine using Oracles VirtualBox. I use this as my sandbox for all of my dev work and sometimes even use virtual environments like venv and docker containers within this virtual machine!
It is somewhat similar to a virtual environment, however it differs because it is designed to function as a full virtualized operating system! You can install different Linux distros and even MacOS and use it as you would a regular computer.
If you want to check out what virtualbox is and how to use it I'll be making a post about it very soon!
With Win10 you can do in WSL2 whatever you are doing in Virtual Box I think
I like to keep my environments separated :)
I feel I've written this boilerplate too many times... isn't there a mongoose-restful or some such library? Like postgREST you get an API just from your table migrations...
postgrest.org/en/v7.0.0/
edit - well I guess just using this wouldn't make a very educational article :/
npmjs.com/package/mongoose-rest-api
Hey Brandon, one small fix
exports.deleteOneRequest is missing the async in the above snippet, so if you run it, you get a syntax error with regards to the await below it.
HTH
Hey Harry! Thanks for commenting. I appreciate the keen eye and I've updated the code snippet ! 😄
Great post. One question: In the updateOneRequest function where the {slug} came from?
Hey Rick, Nice catch! that was an oversite on my part! it should read : {_id:id} not {slug}! my apologies!
Nice post short and concise.
Thanks Andrew!
Hi, I think, some were in
/server/controllers/user.controller.js
is required "usermodel"
?
Hi Filoret, You're totally right, nice catch! I will update that section now ! 😄
Very cool post 👍 thanks.
You're very welcome Calagan! I hope I was able to provide some value for your time! 😄
Thank you Max! I'm hoping to improve with each post 😅
Some comments have been hidden by the post's author - find out more