Learn Step by Step CRUD API using Node.js and Mongoose. This tutorial covers step-by-step instructions for setting up the project, installing dependencies, creating the server, connecting to MongoDB.
Creating the Product model, creating the API routes, mounting the API routes, testing the API, writing Swagger documentation, and deploying to Docker Desktop.
Steps CRUD API using Node.js and Mongoose
Step 1: Setting up the project
Create a new directory for your project and navigate into it.
mkdir nodejs-mongoose-example
cd nodejs-mongoose-example
Step 2: Installing dependencies
Initialize npm in your project and install necessary packages.
npm init -y
npm install express mongoose body-parser swagger-jsdoc swagger-ui-express
Step 3: Creating the server
Create a file named server.js
and set up an Express server.
app.post('/api/login', (req, res) => {
// Query the database for the user
usersCollection.findOne({
username: req.body.username,
password: req.body.password
}, (err, user) => {
if (err) {
console.error('Error finding user:', err);
res.status(500).send('Internal server error');
return;
}
if (user) {
// User found, send success response
res.send('Login successful');
}
})
})
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Run Server: follow below command to run server with node assuming nodemon is not setup in dev env
node server.js
Read Also : What is NoSQL Injection attack in nodejs
Step 4: Connecting to MongoDB
Create a MongoDB Atlas account and obtain the connection string. Replace the connection string in the following code.
// server.js
const mongoose = require('mongoose');
mongoose.connect('mongodb://<username>:<password>@<cluster-url>/<database-name>', {
useNewUrlParser: true,
useUnifiedTopology: true
})
.then(() => console.log('Connected to MongoDB'))
.catch(err => console.error('Error connecting to MongoDB', err));
Step 5: Creating the Product model
Create a models
directory and inside it, create a file named product.js
to define the product schema and model.
// models/product.js
const mongoose = require('mongoose');
const productSchema = new mongoose.Schema({
name: String,
price: Number,
description: String
});
module.exports = mongoose.model('Product', productSchema);
Step 6: Creating the API routes
Create a routes
directory and inside it, create a file named productRoutes.js
to define the API routes for products.
// routes/productRoutes.js
const express = require('express');
const router = express.Router();
const productController = require('../controllers/productController');
router.post('/', productController.createProduct);
router.get('/:id', productController.getProductById);
router.put('/:id', productController.updateProduct);
router.delete('/:id', productController.deleteProduct);
module.exports = router;
Step 7: Mounting the API routes
Mount the product routes in the server.js
file.
// server.js
const productRoutes = require('./routes/productRoutes');
app.use('/api/products', productRoutes);
Step 8: Creating the Product Controller for CRUD Operation
Create a controllers
directory and inside it, create a file named productController.js
to define the CRUD operations for products.
// controllers/productController.js
const Product = require('../models/product');
exports.createProduct = async (req, res) => {
try {
const product = await Product.create(req.body);
res.status(201).json(product);
} catch (err) {
res.status(400).json({ error: err.message });
}
};
// Implement other CRUD operations similar to createProduct
Step 9: Testing the API
let’s write some unit tests using the Jest framework to test the CRUD operations of our Node.js application with MongoDB using Mongoose. We’ll cover positive and negative test cases for each CRUD operation.
First, make sure you have Jest installed in your project:
npm install --save-dev jest supertest
Then, let’s create a tests
directory in the root of your project. Inside this directory, create a file named product.test.js
.
Read Also : 2 Powerful Testing Suits: sinon and chai using nodejs
// tests/product.test.js
const request = require('supertest');
const app = require('../server');
describe('Product API', () => {
let productId;
// Test POST /api/products
describe('POST /api/products', () => {
it('should create a new product', async () => {
const res = await request(app)
.post('/api/products')
.send({ name: 'Test Product', price: 10, description: 'Test description' });
expect(res.statusCode).toEqual(201);
expect(res.body).toHaveProperty('_id');
expect(res.body.name).toEqual('Test Product');
productId = res.body._id;
});
});
// Test GET /api/products/:id
describe('GET /api/products/:id', () => {
it('should fetch the created product', async () => {
const res = await request(app)
.get(`/api/products/${productId}`);
expect(res.statusCode).toEqual(200);
expect(res.body._id).toEqual(productId);
});
it('should return 404 if product not found', async () => {
const res = await request(app)
.get('/api/products/nonexistent-id');
expect(res.statusCode).toEqual(404);
});
});
// Test PUT /api/products/:id
describe('PUT /api/products/:id', () => {
it('should update the created product', async () => {
const res = await request(app)
.put(`/api/products/${productId}`)
.send({ price: 20 });
expect(res.statusCode).toEqual(200);
expect(res.body.price).toEqual(20);
});
it('should return 404 if product not found', async () => {
const res = await request(app)
.put('/api/products/nonexistent-id')
.send({ price: 20 });
expect(res.statusCode).toEqual(404);
});
});
// Test DELETE /api/products/:id
describe('DELETE /api/products/:id', () => {
it('should delete the created product', async () => {
const res = await request(app)
.delete(`/api/products/${productId}`);
expect(res.statusCode).toEqual(200);
});
it('should return 404 if product not found', async () => {
const res = await request(app)
.delete('/api/products/nonexistent-id');
expect(res.statusCode).toEqual(404);
});
});
});
In this test suite:
- We use
supertest
to make HTTP requests to our API. - For each CRUD operation, we have positive test cases to ensure the operation succeeds as expected, and negative test cases to handle error scenarios (e.g., when a product is not found).
- We’re expecting specific HTTP status codes for each operation.
- We’re also checking the response body for specific properties or values.
To run the tests, you can use the following command:
npm test
This will execute the tests using Jest, and you should see the results in your console.
These tests ensure that your API endpoints are functioning correctly and handle error cases appropriately. They help maintain the reliability and stability of your application as you make changes or add new features.
Step 10: Writing Swagger documentation for create, fetch, update, delete
Create a file named swagger.js
to define Swagger documentation for your API.
// swagger.js
const swaggerJSDoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const options = {
definition: {
openapi: '3.0.0',
info: {
title: 'Node.js Mongoose API',
version: '1.0.0',
description: 'API for managing products with Node.js and Mongoose',
},
},
apis: ['./routes/*.js'],
};
const specs = swaggerJSDoc(options);
module.exports = (app) => {
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
};
Mount the Swagger documentation in the server.js
file.
// server.js
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;
require('./swagger')(app); // Mount Swagger documentation
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Step 11: Deploying to Docker Desktop
Create a Dockerfile in the root directory of your project.
# Dockerfile
FROM node:latest
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Build your Docker image.
docker build -t node-mongoose-example .
Run your Docker container.
docker run -p 3000:3000 node-mongoose-example
Conclusion
We have successfully created a Node.js application with MongoDB using Mongoose, added Swagger documentation, and deployed it to Docker Desktop.
We can further enhance this application by adding authentication, validation, error handling, and more features as per your requirements.
Hey Tech Enthusiasts!
I’m Avinash, a passionate tech blogger with over 13+ years of experience in the trenches of software engineering.
You could say I’ve worn many hats in my journey – from full-stack developer crafting beautiful and functional applications to Solution Architect, designing the architecture for complex systems.
Over the years, I’ve delved into a vast arsenal of languages and tools, including the Generative AI (LLMs, LLM-Proxy, Observability, Prompt Engineering), .NET family (.NET, .NET Core), PHP, Rust, Python, the JavaScript frameworks (Angular, React, Node.js), and databases like MySQL, SQL Server, MongoDB.
As the cloud revolutionized our world, I’ve become well-versed in both Azure and GCP platforms, wielding Docker for containerization and CI/CD pipelines to streamline development workflows.
Here on my blog, I aim to share the knowledge I’ve accumulated and the lessons I’ve learned along the way. Whether you’re a seasoned developer or just starting your coding adventure, I want to provide you with insightful, practical articles that tackle real-world tech challenges.
Get ready to explore the latest advancements, delve into programming concepts, and discover efficient solutions to your development dilemmas. So, buckle up, tech enthusiasts – let’s embark on this exciting journey together!