Skip to content

๐Ÿš€ Comprehensive Tutorial: Setting Up MongoDB with Node.js Using Docker Compose

Outline

  • ๐Ÿ› ๏ธ Prerequisites
  • ๐Ÿ“‚ Project Structure
  • ๐Ÿณ Setting Up Docker Compose
  • ๐Ÿ“ Creating the Dockerfile
  • ๐Ÿ“ฆ Building the Node.js Application
  • ๐Ÿ”— Connecting Node.js to MongoDB
  • ๐Ÿ–ฅ๏ธ Creating a Simple HTML Form
  • ๐Ÿƒ Running the Application
  • ๐Ÿงช Testing the Setup
  • ๐Ÿงน Cleaning Up
  • ๐Ÿ“š Conclusion

๐Ÿ› ๏ธ Prerequisites

Before diving into the tutorial, ensure you have the following installed on your system:

  1. Docker: Docker allows you to containerize applications, making them portable and easy to manage.
  2. Docker Compose: A tool for defining and running multi-container Docker applications.
  3. Node.js: JavaScript runtime for building server-side applications.
  4. npm: Node package manager, usually installed alongside Node.js.

Note: Ensure Docker and Docker Compose are running properly by executing docker --version and docker-compose --version in your terminal.


๐Ÿ“‚ Project Structure

Organize your project directory as follows to maintain clarity and manageability:

mongodb-nodejs-docker/
โ”œโ”€โ”€ docker-compose.yml
โ”œโ”€โ”€ docker-compose.dev.yml
โ”œโ”€โ”€ docker-compose.prod.yml
โ”œโ”€โ”€ Dockerfile
โ”œโ”€โ”€ .env
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ package-lock.json
โ”œโ”€โ”€ mongo-data/
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ index.js
โ””โ”€โ”€ README.md

๐Ÿ“„ File Descriptions

File/Directory Description
docker-compose.yml Base Docker Compose configuration for services.
docker-compose.dev.yml Development-specific Docker Compose overrides.
docker-compose.prod.yml Production-specific Docker Compose overrides.
Dockerfile Defines how to build the Docker image for the Node.js application.
.env Stores environment variables securely.
package.json Lists the project's dependencies and scripts.
package-lock.json Automatically generated for locking dependencies.
mongo-data/ Persistent storage for MongoDB data.
src/index.js Main Node.js application file.
README.md Documentation and project overview.

๐Ÿณ Setting Up Docker Compose

Docker Compose allows you to manage multi-container Docker applications. We'll define two services: node-app for the Node.js application and mongodb for the MongoDB database.

๐Ÿ”ง docker-compose.yml (Base Configuration)

Create a docker-compose.yml file in the root directory with the following content:

version: "3.8"

services:
  node-app:
    container_name: node-app-dev
    build:
      context: .
      target: development
    ports:
      - "4002:4002"
    env_file:
      - ./.env
    volumes:
      - ./src:/app/src:ro
      - /app/node_modules
    depends_on:
      - mongodb
    command: npm run start-dev

  mongodb:
    container_name: mongo-dev
    image: mongo:latest
    restart: always
    ports:
      - "27017:27017"
    volumes:
      - mongo-db:/data/db
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example

volumes:
  mongo-db:

๐Ÿ› ๏ธ docker-compose.dev.yml (Development Overrides)

Create a docker-compose.dev.yml for development-specific configurations:

version: "3.8"

services:
  node-app:
    environment:
      - NODE_ENV=development
    volumes:
      - ./src:/app/src
      - /app/node_modules
    command: npm run start-dev

๐Ÿ› ๏ธ docker-compose.prod.yml (Production Overrides)

Create a docker-compose.prod.yml for production-specific configurations:

version: "3.8"

services:
  node-app:
    build:
      target: production
    environment:
      - NODE_ENV=production
    ports:
      - "4002:4002"
    command: npm run start

๐Ÿ“ Creating the Dockerfile

The Dockerfile defines how to build the Docker image for your Node.js application. We'll use multi-stage builds to optimize the image for both development and production environments.

Dockerfile

Create a Dockerfile in the root directory with the following content:

# Stage 1: Base Image
FROM node:18-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./

# Stage 2: Development Stage
FROM base AS development
ENV NODE_ENV=development
RUN npm install
COPY . .
CMD ["npm", "run", "start-dev"]

# Stage 3: Production Stage
FROM base AS production
ENV NODE_ENV=production
RUN npm install --only=production
COPY . .
CMD ["npm", "run", "start"]

๐Ÿ“ Explanation

Stage Purpose Commands
Base Establishes the base environment with dependencies copied. FROM node:18-alpine AS base
WORKDIR /app
COPY package.json package-lock.json ./
Development Sets up the development environment with all dependencies installed. FROM base AS development
ENV NODE_ENV=development
RUN npm install
COPY . .
CMD ...
Production Sets up the production environment with only production dependencies. FROM base AS production
ENV NODE_ENV=production
RUN npm install --only=production
COPY . .
CMD ...

Note: Multi-stage builds help in reducing the final image size by only including necessary files and dependencies for each environment.


๐Ÿ“ฆ Building the Node.js Application

We'll create a simple Node.js application using Express and Mongoose to interact with MongoDB.

Step 1: Initialize the Project

Navigate to your project directory and initialize a new Node.js project:

npm init -y

Step 2: Install Dependencies

Install the necessary dependencies:

npm install express mongoose
npm install --save-dev nodemon
  • express: Web framework for Node.js.
  • mongoose: ODM for MongoDB.
  • nodemon: Utility that automatically restarts the server on file changes (development only).

Step 3: Update package.json Scripts

Modify the package.json to include scripts for development and production:

{
  "name": "mongodb-nodejs-docker",
  "version": "1.0.0",
  "description": "A simple Node.js app with MongoDB using Docker Compose",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "start-dev": "nodemon src/index.js"
  },
  "author": "Your Name",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^6.7.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.20"
  }
}

๐Ÿ”— Connecting Node.js to MongoDB

We'll set up Mongoose to connect to the MongoDB service defined in Docker Compose.

src/index.js

Create a src directory and within it, create an index.js file with the following content:

const express = require("express");
const mongoose = require("mongoose");
const path = require("path");

const app = express();

// Middleware to parse JSON and URL-encoded data
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Serve the HTML form on the root route
app.get("/", (req, res) => {
  res.send(`
    <html>
      <head>
        <title>Create Todo</title>
        <style>
          body {
            font-family: Arial, sans-serif;
            background-color: #f4f4f4;
            padding: 20px;
          }
          h1 {
            color: #333;
          }
          form {
            background: #fff;
            padding: 20px;
            border-radius: 5px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            max-width: 400px;
            margin: auto;
          }
          label {
            display: block;
            margin-bottom: 10px;
            font-weight: bold;
          }
          input, textarea {
            width: 100%;
            padding: 10px;
            margin-bottom: 20px;
            border: 1px solid #ccc;
            border-radius: 4px;
          }
          button {
            background-color: #5cb85c;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 16px;
          }
          button:hover {
            background-color: #4cae4c;
          }
        </style>
      </head>
      <body>
        <h1>Add a New Todo</h1>
        <form id="todoForm" method="POST" action="/todoCreate">
          <label for="taskName">Task Name:</label>
          <input type="text" id="taskName" name="taskName" required>

          <label for="description">Description:</label>
          <textarea id="description" name="description" rows="4" required></textarea>

          <button type="submit">Add Todo</button>
        </form>
      </body>
    </html>
  `);
});

// MongoDB connection details
const BD_USER = process.env.BD_USER || "root";
const BD_PASSWORD = process.env.BD_PASSWORD || "example";
const BD_PORT = process.env.BD_PORT || "27017";
const BD_NAME = process.env.BD_NAME || "mydatabase";
const BD_HOST = process.env.BD_HOST || "mongodb"; // Service name from docker-compose

const URI = `mongodb://${BD_USER}:${BD_PASSWORD}@${BD_HOST}:${BD_PORT}/${BD_NAME}`;

// Connect to MongoDB
mongoose
  .connect(URI)
  .then(() => {
    console.log("Connected to MongoDB");
  })
  .catch((err) => {
    console.error("MongoDB connection error:", err);
  });

// Define the Todo schema
const todoSchema = new mongoose.Schema({
  taskName: { type: String, required: true },
  description: { type: String, required: true },
});

const Todo = mongoose.model("Todo", todoSchema);

// API endpoint to create a new Todo item
app.post("/todoCreate", async (req, res) => {
  const { taskName, description } = req.body;

  const newTodo = new Todo({
    taskName,
    description,
  });

  try {
    await newTodo.save();
    res.send(`
      <h1>Todo Created Successfully!</h1>
      <a href="/">Add another</a>
    `);
  } catch (err) {
    res.status(500).send(`
      <h1>Error Adding Todo</h1>
      <p>${err.message}</p>
      <a href="/">Go back</a>
    `);
  }
});

// Run server
const PORT = process.env.PORT || 4002;
app.listen(PORT, () => {
  console.log(`Listening on port ${PORT}`);
});

๐Ÿ” Explanation

  1. Dependencies: We import express, mongoose, and path to set up the server and connect to MongoDB.

  2. Middleware:

  3. express.json(): Parses incoming JSON requests.

  4. express.urlencoded({ extended: true }): Parses URL-encoded bodies (from form submissions).

  5. Routes:

  6. GET /: Serves an HTML form for creating new Todo items.

  7. POST /todoCreate: Handles form submissions, creates a new Todo document in MongoDB, and responds with a success or error message.

  8. MongoDB Connection:

  9. Connection URI is constructed using environment variables for flexibility.

  10. Mongoose connects to MongoDB using the provided URI.
  11. Proper error handling ensures any connection issues are logged.

  12. Todo Schema:

  13. Defines the structure of Todo documents with taskName and description fields.

  14. Server:

  15. Listens on port 4002 or the port specified in the environment variables.

๐Ÿ”— Connecting Docker Compose with the Application

Ensure that your Docker Compose configuration correctly links the node-app service with the mongodb service.

๐Ÿ“ docker-compose.yml Adjustments

Ensure the BD_HOST in your Node.js application matches the service name of MongoDB in docker-compose.yml, which is mongodb.

Also, make sure that the node-app service depends on the mongodb service to ensure MongoDB starts first.

Updated src/index.js

Review the connection details in your src/index.js to ensure they match the Docker Compose setup:

const BD_HOST = process.env.BD_HOST || "mongodb"; // Service name from docker-compose

๐Ÿ–ฅ๏ธ Creating a Simple HTML Form

We've embedded the HTML form directly within the Express route (GET /). However, you can enhance the UI using frameworks like Bootstrap for better styling. Here's how you can modify the form with Bootstrap:

Enhanced HTML Form with Bootstrap

Update the app.get("/") route in src/index.js:

app.get("/", (req, res) => {
  res.send(`
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Create Todo</title>
      <!-- Bootstrap CSS -->
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    </head>
    <body>
      <div class="container mt-5">
        <h1 class="text-center">Add a New Todo</h1>
        <form id="todoForm" method="POST" action="/todoCreate">
          <div class="mb-3">
            <label for="taskName" class="form-label">Task Name:</label>
            <input type="text" class="form-control" id="taskName" name="taskName" required>
          </div>
          <div class="mb-3">
            <label for="description" class="form-label">Description:</label>
            <textarea class="form-control" id="description" name="description" rows="4" required></textarea>
          </div>
          <button type="submit" class="btn btn-primary">Add Todo</button>
        </form>
      </div>
      <!-- Bootstrap JS -->
      <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
    </body>
    </html>
  `);
});

Note: Including Bootstrap via CDN enhances the form's appearance without additional configuration.


๐Ÿƒ Running the Application

With all configurations in place, let's build and run the application using Docker Compose.

Step 1: Create the .env File

Create a .env file in the root directory to securely store environment variables:

BD_USER=root
BD_PASSWORD=example
BD_HOST=mongodb
BD_PORT=27017
BD_NAME=mydatabase
PORT=4002

Step 2: Build and Start Containers

Navigate to your project directory and run the following command to build and start the Docker containers:

docker-compose up --build

Explanation:

  • --build: Forces a rebuild of the Docker images.
  • up: Creates and starts the containers.
  • Services Started:
  • mongodb: MongoDB database.
  • node-app: Node.js application.

Step 3: Accessing the Application

Once the containers are up and running:

  1. Open the Form: Navigate to http://localhost:4002/ in your web browser to access the Todo creation form.

  2. Add a Todo: Fill in the Task Name and Description fields and click Add Todo.

  3. Confirmation: Upon successful submission, you should see a confirmation message. You can verify the addition by connecting to MongoDB and checking the todos collection.


๐Ÿงช Testing the Setup

To ensure everything is working correctly, perform the following tests:

Test 1: Adding a Todo

  1. Access the Form: Go to http://localhost:4002/.

  2. Submit a Todo:

  3. Task Name: Buy groceries

  4. Description: Milk, Bread, Eggs

  5. Confirm Addition: You should receive a success message indicating the Todo was created.

Test 2: Verifying in MongoDB

  1. Connect to MongoDB Shell:

If you're using Docker, you can access the MongoDB container's shell:

docker exec -it mongo-dev mongosh -u root -p example --authenticationDatabase admin
  1. Switch to Database:
use mydatabase
  1. View Todos Collection:
db.todos.find().pretty();

You should see the Todo item you just added.

Test 3: Handling Errors

  1. Submit an Incomplete Form: Try submitting the form without filling in the required fields to ensure validation works.

  2. Incorrect Credentials: Modify the .env file with incorrect MongoDB credentials and observe if the application handles connection errors gracefully.


๐Ÿงน Cleaning Up

After testing, you might want to stop and remove the Docker containers to free up system resources.

Stop and Remove Containers

docker-compose down

Note: This command stops and removes the containers but retains the data in the mongo-data volume.

Remove Volumes (Optional)

If you want to remove the persistent data as well:

docker-compose down -v

Warning: This will delete all data stored in MongoDB.