Build Your First Backend Project: A Simple Blog API with Express and MongoDB

Faith NjahFaith Njah
12 min read

When you’re just starting out as a developer, it’s usually easy to understand basic concepts like variables, functions, and arrays. But putting those concepts together to build a real project? That’s where a lot of beginners hit a wall.

If that sounds like you, you’re in the right place.

By the end of this tutorial, you won’t just understand how the backend works, you’ll also build a complete, database-connected Blog API that you can actually show off in your portfolio.

What You’ll Learn

  • How to perform full CRUD operations (Create, Read, Update, Delete)

  • How to use dummy data to understand logic before dealing with databases

  • How to connect your API to a real database using MongoDB

Prerequisites

  • Node.js and npm installed— Download here

  • Basic JavaScript knowledge (variables, functions, arrays, objects)

  • Basic familiarity with Node.js and Express — like setting up a simple server

Let’s get started.

Step 1: Set up Your Project

To initialize the project and install the necessary dependencies, open your terminal and run the appropriate commands. First, use npm init -y to quickly set up a new Node.js project with default settings. Then, install the required packages for building the server:

npm init -y                # Initializes a new Node.js project  
npm install express        # Installs the Express framework  
npm install --save-dev nodemon  # For auto-restarting your server during development

To create the entry point of your application, navigate to the root folder of your project and create a new file named index.js. This file will serve as the main starting point of your server. Inside it, you'll add the necessary setup code to initialize Express and define your application's basic behavior, as shown below:

const express = require('express');
const app = express();
const port = 3000;

// A test route
app.get('/', (req, res) => {
  res.send('Welcome to the Blog API');
});

// Start the server
app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

Run the Server:

To start the server with nodemon, run:

nodemon

If everything is set up correctly, you should see:

Server running on http://localhost:3000

Now, open your browser and go to http://localhost:3000. You should see:

“Welcome to the Blog API”

Nice! You’ve just created your backend server.

Step 2: Creating a Route Folder and Adding Your First Route File

After setting up the basic Express server in index.js, it’s good practice to move all routes into their own folder. That keeps your code tidy and makes it easier to grow the project later. So your project folder would look this way:

project/
│
├── routes/
│   └── blogRoutes.js     ← ➊ new file you’ll create now
│
├── index.js              ← main entry point
└── package.json

Next, inside the blogRoutes.js file, you should have this:

const express = require('express');
const router  = express.Router();

/**
 * GET /api/blogs
 * Returns a simple message for now.
**/
router.get('/blogs', (req, res) => {
  res.send('Hello from the blog route!');
});

module.exports = router;

Here’s what’s happening:

You started by importing Express and creating a router using express.Router(). This helps you group related routes and keep your codebase organized. Then, you define a route. TheGET request is to handle incoming requests and send responses. After setting up your routes, you export the router so it can be used in other parts of your app.

Next, you go to your index.js file, import the router, and register it with your Express app using app.use(). This wires the routes into your main server so they become active and accessible.

Your index.js file will look like this:

const express    = require('express');
const app        = express();
const port       = 3000;

// ➊ Import the routes you just created
const blogRoutes = require('./routes/blogRoutes');

// ➋ Mount them under /
app.use('/', blogRoutes);

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`);
});

💡 Why use app.use('/', blogRoutes)?

The app.use() is a middleware function. You're telling Express to mount all routes from blogRoutes at the root (/). When you start your server and visit http://localhost:3000/blogs, you should see:

Hello from the blog route!

Congrats! Your routing setup is done and you’re ready to add real CRUD logic in the next step.

Now that the server is up and running, it’s time to understand how routing and CRUD operations work by using dummy data. Before jumping into MongoDB, we’ll build out the API logic using a simple in-memory array of articles. This helps you focus purely on how routes work, without worrying about databases just yet.

Step 3: Working with Dummy Data

To work with dummy data, you can define a sample array of articles in your blogRoute.js file. This allows you to work with data before connecting a real database.

Add the following array to your code:

let articles = [
  { id: 1, title: "A Note on APIs", content: "This is a new note on APIs..." },
  { id: 2, title: "Another Article", content: "This is another article..." },
  { id: 3, title: "Learning Express", content: "Express makes backend easy!" },
];

Get Route

To retrieve all articles in the array, define a GET route and return the articles array. This allows you to display the full list of articles when a user visits the route.



router.get('/blogs', (req, res) => {
  res.json(articles);
});

To fetch one specific article, you use a route parameter like :id. This allows you to grab the article’s ID directly from the URL using req.params. Once you have the ID, use the .find() method to search for the article in the array. If it exists, return it. If not, send a 404 error.

router.get('/article/:id', (req, res) => {
  const articleId = req.params.id;
  const article = articles.find(a => a.id == articleId);

  if (article) {
    res.json(article);
  } else {
    res.status(404).json({ message: 'Article not found' });
  }
});

So far, you’ve implemented both:

  • GET /blogs → to fetch all articles

  • GET /article/:id → to fetch a single article by ID

With that, the Read portion of CRUD is done! Here’s what your final blogRoutes.js file looks like so far:

const express = require('express');
const router = express.Router();

let articles = [
  { id: 1, title: "A Note on APIs", content: "This is a new note on APIs..." },
  { id: 2, title: "Another Article", content: "This is another article..." },
  { id: 3, title: "Learning Express", content: "Express makes backend easy!" },
];

// Get all articles
router.get('/blogs', (req, res) => {
  res.json(articles);
});

// Get a single article by ID
router.get('/article/:id', (req, res) => {
  const articleId = req.params.id;
  const article = articles.find(a => a.id == articleId);

  if (article) {
    res.json(article);
  } else {
    res.status(404).json({ message: 'Article not found' });
  }
});

module.exports = router;

You’ve now completed the Read part of CRUD. Visiting /blogs displays all articles, while /article/:id lets you fetch a single article using its ID from the URL.

Post Route

Next is the Create part. This uses the POST method to let users add a new article. When a POST request is made to /article, the server pulls the title and content from req.body, creates a new article object, adds it to the array, and sends it back in the response. This marks your first step into the "C" of CRUD.

Just this way:


router.post('/article', (req, res) => {
  const newArticle = {
    id: articles.length + 1,
    title: req.body.title,
    content: req.body.content
  };

  articles.push(newArticle);
  res.json(newArticle);
});

Using Postman, you can easily test your POST route. Enter the POST route /article, go to the Body tab, choose raw and set the type to JSON.
Then, enter the data like this:

{
  "title": "My First Blog",
  "content": "This is the content of the blog."
}

Hit Send, and you’ll get the newly created article with its id, title, and content in the response.

Put Route

You’ve now moved into the Update part of CRUD. To update an existing article, you use the PUT method and pass the article's ID in the URL like /article/:id. The server finds the article using the ID, updates the title and content with the data sent in req.body, and returns the updated article.


router.put('/article/:id', (req, res) => {
  const articleId = req.params.id;
  const index = articles.findIndex(a => a.id == articleId);

  if (index === -1) {
    return res.status(404).json({ message: 'Article not found' });
  }

  const updatedArticle = {
    id: parseInt(articleId),
    title: req.body.title,
    content: req.body.content,
  };

  articles[index] = updatedArticle;
  res.json(updatedArticle);
});

To test in Postman, send a PUT request to /article/:id, choose raw JSON in the body, and enter the new title and content.

Delete Route

You’ve now reached the Delete part of CRUD. To remove an article, you use the DELETE method with the article's ID in the URL like /article/:id. The server finds the article by ID, removes it from the array, and sends back a confirmation message.


router.delete('/article/:id', (req, res) => {
  const articleId = parseInt(req.params.id);
  const index = articles.findIndex(a => a.id === articleId);

  if (index !== -1) {
    articles.splice(index, 1);
    res.json({ message: 'Article deleted successfully' });
  } else {
    res.status(404).json({ message: 'Article not found' });
  }
});

To test in Postman, send a DELETE request to /article/:id and confirm that the article is removed from the list.

So far, you have defined a route for each HTTP method: GET, POST, PUT, and DELETE. To fetch, update, or delete a specific article, you used req.params.id to extract the article's ID from the URL. For creating or updating articles, you used req.body to access the data sent by the client.

Every change was made directly to the in-memory articles array.


const express = require('express');
const router = express.Router();

let articles = [
  { id: 1, title: 'First Article', content: 'This is the first article' },
  { id: 2, title: 'Second Article', content: 'This is the second article' }
];

// GET all articles
router.get('/blogs', (req, res) => {
  res.json(articles);
});

// GET a single article by ID
router.get('/article/:id', (req, res) => {
  const articleId = parseInt(req.params.id);
  const article = articles.find(a => a.id === articleId);
  if (!article) {
    return res.status(404).json({ message: 'Article not found' });
  }
  res.json(article);
});

// POST (Create) a new article
router.post('/article', (req, res) => {
  const newArticle = {
    id: articles.length + 1,
    title: req.body.title,
    content: req.body.content
  };
  articles.push(newArticle);
  res.status(201).json(newArticle);
});

// PUT (Update) an article by ID
router.put('/article/:id', (req, res) => {
  const articleId = parseInt(req.params.id);
  const article = articles.find(a => a.id === articleId);
  if (!article) {
    return res.status(404).json({ message: 'Article not found' });
  }
  article.title = req.body.title || article.title;
  article.content = req.body.content || article.content;
  res.json(article);
});

// DELETE an article by ID
router.delete('/article/:id', (req, res) => {
  const articleId = parseInt(req.params.id);
  const index = articles.findIndex(a => a.id === articleId);
  if (index === -1) {
    return res.status(404).json({ message: 'Article not found' });
  }
  articles.splice(index, 1);
  res.json({ message: 'Article deleted successfully' });
});

module.exports = router;

At this stage, there's no database, just logic and routes to help you understand how a real API is structured.

Step 4: Adding Database

Before connecting to a database, it’s important to understand how routes work on their own. That’s why we started with dummy data using an in-memory array.

But in real-world projects, you’ll be working with a database, whether it’s MongoDB, PostgreSQL, or something else. So after playing around with these routes (GET, POST, PUT, DELETE), you need a real database to persist and retrieve actual data. This is where your app starts becoming more powerful and realistic. Now that we’ve tested things with dummy data, let’s take a real step forward:

Local MongoDB Installation

Project Structure Update

Inside your project folder, create two new folders:

  • models/ – This is where you’ll define your data structure.

  • database/ – This will hold the file to handle the MongoDB connection.

You’ll end up with a structure like this:

project-folder/
│
├── routes/
├── models/
│   └── index.js      ← Mongoose model lives here
├── database/
│   └── index.js      ← MongoDB connection logic
└── index.js          ← Entry point

Install Mongoose

Mongoose is a popular ODM (Object Data Modeling) library that makes working with MongoDB easier.

Open your terminal and run:

npm install mongoose

Create Your Mongoose Model (models/index.js)

This file describes the structure of the data coming into your app. Since we’re working with articles, we’ll define a simple schema with a title and content.

// models/index.js
const mongoose = require('mongoose');

const articleSchema = new mongoose.Schema({
    title: {
        type: String,
        required: true
    },
    content: {
        type: String,
        required: true
    }
}, {
    timestamps: true  // Adds createdAt and updatedAt fields
});

const data = mongoose.model('Article', articleSchema);
module.exports = { data };

Connect to MongoDB (database/index.js)

This file sets up a connection to your MongoDB database.

// database/index.js
const mongoose = require('mongoose');

const url = 'mongodb://localhost:27017/blog-api';

const connectToDatabase = async () => {
    const connect = await mongoose.connect(url);
    if (connect) {
        console.log('Database unlocked!');
    }
};

module.exports = { connectToDatabase };

Update Your Route File

Now that we’re using a real database, we no longer need the dummy array. Instead, we use the data model we just defined.

// routes/index.js
const express = require("express");
const router = express.Router();
const { data } = require('../models/index');

// Get all articles
router.get('/', async (req, res) => {
    try {
        const articles = await data.find();
        res.json(articles);
    } catch (error) {
        res.status(500).json({ message: 'Error fetching articles' });
    }
});

// Get one article
router.get('/article/:id', async (req, res) => {
    try {
        const article = await data.findById(req.params.id);
        if (article) {
            res.json(article);
        } else {
            res.status(404).json({ message: 'Article not found' });
        }
    } catch (error) {
        res.status(400).json({ message: 'Invalid article ID format' });
    }
});

// Post an article
router.post('/article', async (req, res) => {
    try {
        const newArticle = new data({
            title: req.body.title,
            content: req.body.content
        });

        const savedArticle = await newArticle.save();
        res.json(savedArticle);
    } catch (error) {
        res.status(400).json({ message: error.message });
    }
});

// Edit an article
router.put('/:id', async (req, res) => {
    try {
        const updatedArticle = await data.findByIdAndUpdate(
            req.params.id,
            {
                title: req.body.title,
                content: req.body.content
            },
            { new: true }
        );

        if (updatedArticle) {
            res.json(updatedArticle);
        } else {
            res.status(404).json({ message: 'Article not found' });
        }
    } catch (error) {
        res.status(400).json({ message: 'Error updating article' });
    }
});

// Delete an article
router.delete('/:id', async (req, res) => {
    try {
        const deletedArticle = await data.findByIdAndDelete(req.params.id);
        if (deletedArticle) {
            res.json({ message: 'Deleted successfully' });
        } else {
            res.status(404).json({ message: 'Article not found' });
        }
    } catch (error) {
        res.status(400).json({ message: 'Error deleting article' });
    }
});

module.exports = router;

Update Entry File (index.js)

Make sure everything is wired up, connect the database and use the routes.

// index.js
const express = require('express');
const port = 8000;
const routes = require('./routes/index');
const { connectToDatabase } = require('./database/index');
const app = express();

app.use(express.json());
app.use('/', routes);

app.listen(port, () => {
    connectToDatabase().then(() => {
        console.log(`Server is running on port ${port}`);
    });
});

Now that you’ve set up your routes and connected them to your MongoDB database using Mongoose, the next step is to run your server and test everything works as expected.

Start your server with nodemon

Using a tool like Postman, or your browser (for simple GET requests) to hit your routes. Check that:

  • You can fetch all blog posts (GET)

  • View a single post by ID (GET /:id)

  • Create a new post (POST)

  • Update a post (PUT)

  • Delete a post (DELETE)

At this point, you’re confirming that the backend logic and database are working seamlessly together. While there’s always room for improvements and new features, you’ve laid a solid foundation with real data being created, retrieved, updated, and deleted from your MongoDB database using Express and Mongoose.

Congratulations! You’ve officially built a working backend API for your blog!

0
Subscribe to my newsletter

Read articles from Faith Njah directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Faith Njah
Faith Njah

Software Developer || Technical Writer || Community Manager