Skip to content

ExpressJS - Database

A dynamic web application needs a database to persist data. This tutorial will guide you through connecting a modern Express.js application to a MongoDB database using Mongoose, an elegant Object Data Modeling (ODM) library for Node.js.

Before starting, ensure you have MongoDB installed and running on your system or have access to a cloud-based MongoDB service (like MongoDB Atlas).

  1. Install Mongoose: npm install mongoose
  2. Install dotenv for Environment Variables: This is a crucial best practice for managing sensitive data like database connection strings. npm install dotenv

Create a file named .env in your project’s root directory. Never commit this file to version control.

# .env file
# Your MongoDB connection string
MONGO_URI=mongodb://127.0.0.1:27017/myModernAppDB
# Your application port
PORT=3000

We will create a dedicated module to handle the database connection. This promotes clean code and separation of concerns.

import mongoose from 'mongoose';
const connectDB = async () => {
try {
const conn = await mongoose.connect(process.env.MONGO_URI);
console.log(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1); // Exit process with failure
}
};
export default connectDB;

In your main server file (server.js or app.js), you can now establish the connection.

server.js
import express from 'express';
import dotenv from 'dotenv';
import connectDB from './config/db.js';
// Load env vars
dotenv.config();
// Connect to database
connectDB();
const app = express();
// ... rest of your app configuration

A Schema defines the structure of your documents within a collection, including field types and validation. A Model is a constructor compiled from a Schema definition, providing an interface to the database for creating, querying, updating, and deleting documents.

import mongoose from 'mongoose';
const personSchema = new mongoose.Schema({
name: {
type: String,
required: [true, 'Please provide a name'],
trim: true
},
age: {
type: Number,
required: [true, 'Please provide an age'],
min: [0, 'Age cannot be negative']
},
nationality: {
type: String,
required: false,
default: 'Not specified'
}
}, {
timestamps: true // Automatically adds createdAt and updatedAt fields
});
const Person = mongoose.model('Person', personSchema);
export default Person;

Modern JavaScript’s async/await syntax drastically simplifies asynchronous database operations, making code more readable and maintainable than old callback-based approaches. All database calls should be wrapped in try...catch blocks for robust error handling.

// In your routes file (e.g., routes/people.js)
import Person from '../models/Person.js';
// @desc Create a new person
// @route POST /api/people
export const createPerson = async (req, res) => {
try {
// req.body contains the parsed JSON from the request
const newPerson = await Person.create(req.body);
res.status(201).json({
success: true,
data: newPerson
});
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
};
// @desc Get all people
// @route GET /api/people
export const getPeople = async (req, res) => {
try {
const people = await Person.find();
res.status(200).json({
success: true,
count: people.length,
data: people
});
} catch (error) {
res.status(500).json({ success: false, error: 'Server Error' });
}
};

Updating a Document (PATCH /api/people/:id)

Section titled “Updating a Document (PATCH /api/people/:id)”

Note: We use PATCH for partial updates, which is a modern RESTful best practice. The old Model.update and Model.findByIdAndUpdate have been superseded. Modern Mongoose recommends fetching the document, updating it, and then saving it to trigger middleware, or using findOneAndUpdate with specific options.

// @desc Update a person
// @route PATCH /api/people/:id
export const updatePerson = async (req, res) => {
try {
const person = await Person.findByIdAndUpdate(
req.params.id,
req.body,
{ new: true, runValidators: true } // Options
);
if (!person) {
return res.status(404).json({ success: false, error: 'Person not found' });
}
res.status(200).json({ success: true, data: person });
} catch (error) {
res.status(400).json({ success: false, error: error.message });
}
};

Deleting a Document (DELETE /api/people/:id)

Section titled “Deleting a Document (DELETE /api/people/:id)”

Note: The remove() method is deprecated. Use deleteOne(), deleteMany(), or findByIdAndDelete().

// @desc Delete a person
// @route DELETE /api/people/:id
export const deletePerson = async (req, res) => {
try {
const person = await Person.findByIdAndDelete(req.params.id);
if (!person) {
return res.status(404).json({ success: false, error: 'Person not found' });
}
res.status(200).json({ success: true, data: {} }); // Or send a confirmation message
} catch (error) {
res.status(500).json({ success: false, error: 'Server Error' });
}
};

Use a tool like Postman, Insomnia, or a command-line tool like curl to test your endpoints. For example, to create a new person:

curl -X POST http://localhost:3000/api/people \
-H 'Content-Type: application/json' \
-d '{"name":"Jane Doe","age":30,"nationality":"Canadian"}'