In the constantly changing realm of web development, creating robust APIs is essential for seamless communication between applications. Representational State Transfer (REST) APIs, known for their simplicity and adaptability, have emerged as a universal preference among developers globally. In this guide, we will go through the process of building and consuming a RESTful CRUD API using the popular Node.js runtime environment and the Express.js framework by building a Goal Tracker.
Understanding CRUD
Before we get started, let's understand what a RESTful CRUD API is. CRUD stands for Create
, Read
, Update
, and Delete
. It's an acronym used in computer programming to describe the four basic functions of a persistent storage system, like a database. These operations allow users to interact with data by creating new records, reading existing records, updating existing records, and deleting records that are no longer needed.
CRUD operations are fundamental in web development because they enable applications to interact with and manage data effectively, providing a seamless experience for users.
RESTful API Standards
Routes | Description |
---|---|
GET /goals | Get goals |
GET /goals/1 | Get goal with id of 1 |
POST /goals | Add a goal |
PUT /goals/1 | Update goal with id of 1 |
DELETE /goals/1 | Delete goal with id of 1 |
Setting Up Your Development Environment
Before we start building our API, we first need to set up our development environment.
-
Create a new directory:
mkdir goal-tracker
-
Navigate to your directory that you just created with the following:
cd goal-tracker
-
Initialize your Node.js project:
npm init -y
Setting Up Our Server
Installing Express & Environment Variables
-
Install
express
&dotenv
npm install express dotenv
Â
Installing Nodemon
Nodemon is used to automatically restart the node application when a file changes.
-
Install
nodemon
npm install -D nodemon
Â
Installing Colors
-
Install
colors
(Optional - This just allows us to be able to distinguish between different messages within our console)
npm install colors
- Import
colors
inserver.js
to be able to use it
import colors from 'colors';
- Import
Â
Updating Our Scripts
-
Update your scripts in the
package.json
to include the following:
{ "name": "goal-tracker", "version": "1.0.0", "description": "A goal tracking application to keep track of your goals", "main": "server.js", "scripts": { "start": "SET NODE_ENV=production & node server", "dev": "nodemon server" }, "keywords": [], "author": "Dylan Sleith", "license": "ISC", }
Â
Adding Our Environment Variables
-
Create a file on the root of your project called
.env
and add the following:
NODE_ENV=development PORT=5000
Â
Creating Our CRUD Routes
-
First let's allow us to be able to use ES Modules in Node.js by adding the following into our
package.json
{ // ... "type": "module" }
-
Let's add a basic setup for our routes. As a boilerplate, we can use the following code to set up our server with different routes depending on our CRUD operations. We will be refactoring this later on!
import express from 'express'; import dotenv from 'dotenv'; import colors from 'colors'; // Load environment variables dotenv.config(); // Initialize Express const app = express(); // CRUD Routes app.get('/api/v1/goals', (req, res) => { res.status(200).json({ success: true, msg: 'Show all goals'}); }); app.get('/api/v1/goals/:id', (req, res) => { res.status(200).json({ success: true, msg: `Get goal ${req.params.id}` }); }); app.post('/api/v1/goals', (req, res) => { res.status(200).json({ success: true, msg: 'Create new goal'}); }); app.put('/api/v1/goals/:id', (req, res) => { res.status(200).json({ success: true, msg: `Update goal ${req.params.id}` }); }); app.delete('/api/v1/goals/:id', (req, res) => { res.status(200).json({ success: true, msg: `Delete goal ${req.params.id}` }); }); // Port const PORT = process.env.PORT || 5000; // Listen for requests app.listen( PORT, console.log( `Server is running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow .bold ) );
-
Now, let's separate our routes into its own folder so everything is not in the main
server.js
file. Create a new file in/routes
calledgoals.js
and move the routes fromserver.js
to the new file,goals.js
.
import express from 'express'; const router = express.Router(); // CRUD Routes router.get('/', (req, res) => { res.status(200).json({ success: true, msg: 'Show all goals' }); }); router.get('/:id', (req, res) => { res.status(200).json({ success: true, msg: `Get goal ${req.params.id}` }); }); router.post('/', (req, res) => { res.status(200).json({ success: true, msg: 'Create new goal'}); }); router.put('/:id', (req, res) => { res.status(200).json({ success: true, msg: `Update goal ${req.params.id}`}); }); router.delete('/:id', (req, res) => { res.status(200).json({ success: true, msg: `Delete goal ${req.params.id}`}); }); export default router;
-
Now, update
server.js
with the following:
import express from 'express'; import dotenv from 'dotenv'; import colors from 'colors'; import goals from './routes/goals.js'; // Load environment variables dotenv.config(); // Initialize Express const app = express(); // Mount routers app.use('/api/v1/goals', goals); // "/api/v1/goals" => "/" // Port const PORT = process.env.PORT || 5000; // Listen for requests app.listen( PORT, console.log( `Server is running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow .bold ) );
Creating Our Controllers
Controllers are responsible for handling incoming requests and returning responses to the client.
-
Let's create our controllers. Create a new file in
/controllers
calledgoals.js
and add the following controllers:
// Goals CRUD Routes // @desc Get all goals // @route GET /api/v1/goals // @access Public export const getGoals = (req, res, next) => { res.status(200).json({ success: true, msg: 'Show all goals' }); }; // @desc Get single goal // @route GET /api/v1/goals/:id // @access Public export const getGoal = (req, res, next) => { res.status(200).json({ success: true, msg: `Get goal ${req.params.id}` }); }; // @desc Create new goal // @route POST /api/v1/goals // @access Private export const createGoal = (req, res, next) => { res.status(200).json({ success: true, msg: 'Create new goal' }); }; // @desc Update goal // @route PUT /api/v1/goals/:id // @access Private export const updateGoal = (req, res, next) => { res.status(200).json({ success: true, msg: `Update goal ${req.params.id}` }); }; // @desc Delete goal // @route DELETE /api/v1/goals/:id // @access Private export const deleteGoal = (req, res, next) => { res.status(200).json({ success: true, msg: `Delete goal ${req.params.id}` }); };
-
Now we need to update our
routes.js
file to the following:
import express from 'express'; const router = express.Router(); // Import Controllers import { getGoals, getGoal, createGoal, updateGoal, deleteGoal, } from '../controllers/goals.js'; // Attach Controllers to Routes router.route('/').get(getGoals).post(createGoal); router.route('/:id').get(getGoal).put(updateGoal).delete(deleteGoal); // Export router export default router;
Setting Up & Connecting To Our Database
Creating Your Project
Go to https://account.mongodb.com/account/login and log in
Click Projects > New Project
Enter your project name, in this case we will use GoalTracker, then click Next
Now click Create Project
Â
Creating Your Project
Make sure you are on the Overview tab, then click Create
-
Use the following to setup your Cluster
- Cluster: M0
- Provider: AWS
- Region: us-east-1
- Name: (Your Cluster Name)
Now click Create
Â
Creating A Database User
Make sure you are on the Database Access tab, then click Add New Database User
-
Use the following to create your Database User
- Authentication Method: Password
-
Password Authentication:
- Username: (Your Username)
- Password: (Your Password)
- Database User Privileges: Read and write to any database
Now click Add User
Â
Whitelisting Our IP Address
Make sure you are on the Network Access tab, then click Add IP Address
Click Add Current IP Address and it will auto populate the field for you with your IP address or just click Allow Access From Anywhere to allow access from any IP address
Now click Confirm
Â
Connecting To Our Database Using MongoDB Compass
Make sure you are on the Overview tab, then click Connect
Under Access your data through tools, click Compass
Copy the Connection String, then open MongoDB Compass
-
In MongoDB Compass, under New Connection, paste the Connection String that you just copied
mongodb+srv://username:<password>@goaltracker.ofvhjnl.mongodb.net/
Now click Connect
Â
Connecting To Our Database Within Our Project
-
Install
mongoose
npm install mongoose
-
Add a new environment variable to your
.env
file:
MONGO_URI=mongodb+srv://username:<password>@goaltracker.ofvhjnl.mongodb.net/goaltracker
-
Create a new directory called
/lib
and then create a new file in that directory calleddb.js
and add the following:
import mongoose from 'mongoose'; const connectDb = async () => { const conn = await mongoose.connect(process.env.MONGO_URI, { useNewUrlParser: true, useUnifiedTopology: true, }); console.log(`MongoDB Connected: ${conn.connection.host}`.cyan.underline.bold); }; export default connectDb;
-
Now in
server.js
we need to connect to our MongoDB Database, addconnectDb();
after you load your environment variables inserver.js
:
import express from 'express'; import dotenv from 'dotenv'; import colors from 'colors'; import goals from './routes/goals.js'; import connectDb from './lib/db.js'; // Load environment variables dotenv.config(); // Connect to database connectDb(); // Initialize Express const app = express(); // ...
Creating Our Goal Model/Schema
We need to create our Goal Model/Schema in order to describe how are data is going to look like, there are also some other use cases such as data validation, security, etc.
-
Create a new directory called
/model
and then create a new file in that directory calledGoal.js
and add the following:
import mongoose from 'mongoose'; const GoalSchema = new mongoose.Schema({ name: { type: String, required: [true, 'Please add a goal name'], unique: true, trim: true, maxLength: [20, 'Goal name can not be more than 20 characters'], }, priority: { type: String, required: true, enum: ['Low', 'Medium', 'High'], }, status: { type: String, required: true, enum: ['Pending', 'In Progress', 'Completed'], }, createdAt: { type: Date, default: Date.now, }, updatedAt: { type: Date, default: Date.now, }, }); export default mongoose.model('Goal', GoalSchema);
Creating Our CRUD Operations
POST (Create) - Create New Goal
Creating Our POST Request
-
Before we get started, we need to go into our
server.js
file and add theBody Parser
middleware so that we can read data from the request.
import express from 'express'; import dotenv from 'dotenv'; import colors from 'colors'; import goals from './routes/goals.js'; import connectDb from './lib/db.js'; // ... // Body parser app.use(express.json());
-
We need to add the following logic into our createGoal controller to have a working endpoint for our POST request:
// @desc Create new goal // @route POST /api/v1/goals // @access Private export const createGoal = async (req, res, next) => { try { const goal = await Goal.create(req.body); res.status(201).json({ success: true, data: goal, }); } catch (err) { res.status(400).json({ success: false, }); } };
Â
Testing Our POST Request in Postman
Open Postman
Create a new HTTP Request
-
Add the following information to the request
- URL:
http://localhost:5000/api/v1/goals
- Method:
POST
- Headers:
'Content-Type': 'application/json'
- Body (raw JSON):
{ "name": "Learn tRPC", "priority": "Medium", "status": "Completed" }
- URL:
Click Send and you will get a response of 201 Created which means you have successfully created a new record and it will be created in your database.
Â
GET (Read) - Get All Goals
Creating Our GET Request
-
We need to add the following logic into our getGoals controller to have a working endpoint for our GET request:
// @desc Get all goals // @route GET /api/v1/goals // @access Public export const getGoals = async (req, res, next) => { try { const goals = await Goal.find(); res.status(200).json({ success: true, count: goals.length, data: goals }); } catch (err) { res.status(400).json({ success: false }); } };
Â
Testing Our GET Request in Postman
Open Postman
Create a new HTTP Request
-
Add the following information to the request
- URL:
http://localhost:5000/api/v1/goals
- Method:
GET
- URL:
Click Send and you will get a response of 200 OK which means everything went okay and you will get a list of goals displayed back to you.
Â
GET (Read) - Get Single Goal
Creating Our GET Request
-
We need to add the following logic into our getGoal controller to have a working endpoint for our GET request:
// @desc Get single goal // @route GET /api/v1/goals/:id // @access Public export const getGoal = async (req, res, next) => { try { const goal = await Goal.findById(req.params.id); if (!goal) { return res.status(400).json({ success: false }); } res.status(200).json({ success: true, data: goal }); } catch (error) { res.status(400).json({ success: false }); } };
Â
Testing Our GET Request in Postman
Open Postman
Create a new HTTP Request
-
Add the following information to the request
- URL:
http://localhost:5000/api/v1/goals/653fac8d0d988dfba8a35e76
- Method:
GET
- URL:
Click Send and you will get a response of 200 OK which means everything went okay and you will get a single goal displayed back to you depending on the id you chose.
Â
PUT (Update) - Update Goal
Creating Our PUT Request
-
We need to add the following logic into our updateGoal controller to have a working endpoint for our PUT request:
// @desc Update goal // @route PUT /api/v1/goals/:id // @access Private export const updateGoal = async (req, res, next) => { try { const goal = await Goal.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true, }); if (!goal) { return res.status(400).json({ success: false }); } res.status(200).json({ success: true, data: goal }); } catch (err) { res.status(400).json({ success: false, }); } };
Â
Testing Our PUT Request in Postman
Open Postman
Create a new HTTP Request
-
Add the following information to the request
- URL:
http://localhost:5000/api/v1/goals/653fafc50d988dfba8a35e7a
- Method:
PUT
- Headers:
'Content-Type': 'application/json'
- Body (raw JSON):
{ "status": "In Progress" }
- URL:
Click Send and you will get a response of 200 OK which means you have successfully updated an existing record and it will also be updated in your database.
Â
DELETE (Delete) - Delete Goal
Creating Our DELETE Request
-
We need to add the following logic into our deleteGoal controller to have a working endpoint for our DELETE request:
// @desc Delete goal // @route DELETE /api/v1/goals/:id // @access Private export const deleteGoal = async (req, res, next) => { try { const goal = await Goal.findByIdAndDelete(req.params.id); if (!goal) { return res.status(400).json({ success: false }); } res.status(200).json({ success: true, data: {} }); } catch (err) { res.status(400).json({ success: false, }); } };
Â
Testing Our DELETE Request in Postman
Open Postman
Create a new HTTP Request
-
Add the following information to the request
- URL:
http://localhost:5000/api/v1/goals/653fac8d0d988dfba8a35e76
- Method:
DELETE
- URL:
Click Send and you will get a response of 200 OK which means you have successfully deleted a record from your database.
In summary, building a REST API with Node.js is a rewarding endeavour that requires understanding RESTful principles, error handling, and security measures. Leveraging tools like Express.js and staying updated with the latest trends is crucial.
Â
Thanks for reading!
Have a question? Connect with me via Twitter or send me a message at hello@dylansleith.com
Top comments (1)
💛🌴