Building a Blog API with Node.js: Designing the API (Part 2)
Tags: node, mongoose, mongodb, api
Published: 2023-01-08 20:03:46

In [part one](https://dev.to/pda/building-a-blog-api-with-nodejs-introduction-and-setting-up-the-server-part-1-2266) of this series, we set up our development environment and a basic file structure. Now, let's design the API itself. In this article, we'll cover the following topics:

* Deciding on the routes and functionality of our API
    
* Defining the data models for our database
    
* Implementing the data models
    
* Setting up the database connection
    

Let's get started!

## Deciding on the Routes and Functionality

The first step in designing our API is to decide on the routes and functionality we want to include.

Let's outline the requirements for our blog:

* A user should be able to sign up and sign in to the blog app
    
* A blog can be in two states: draft and published
    
* A user should be able to get a list of published articles, whether logged in or not
    
* A user should be able to get a published article, whether logged in or not
    
* A logged in user should be able to create an article.
    
* When an article is created, it should be in draft state.
    
* The author of the article should be able to update the state of the article to published
    
* The author of the article should be able to edit the article in draft or published state
    
* The author of the article should be able to delete the article in draft or published state
    
* The author of the article should be able to get a list of their articles. The endpoint should be paginated and filterable by state
    
* Articles created should have title, cover image, description, tags, author, timestamp, state, read count, reading time and body.
    
* The list of articles endpoint that can be accessed by both logged in and not logged in users should be paginated.
    
    * It should be searchable by author, title and tags.
        
    * It should also be orderable by read count, reading time and timestamp
        
* When a single article is requested, the API should return the author's information with the blog, and the read count of the blog should increase by 1.
    

Considering the outlined requirements, we'll define our routes as follows:

* Blog routes:
    
    * GET /blog: Retrieve a list of all published articles
        
    * GET /blog/:article\_id: Retrieve a single article by its ID
        
* Author routes: We want only authenticated users to have access to these routes and all of the CRUD operations.
    
    * GET /author/blog: Retrieve a list of all published articles created by the user.
        
    * POST /author/blog: Create a new article
        
    * PATCH /author/blog/edit/:article\_id: Update an article by its ID
        
    * PATCH /author/blog/edit/state/:article\_id: Update an article's state
        
    * DELETE /author/blog/:article\_id: Delete an article by its ID
        
* Auth routes: for managing user authentication.
    
    * POST /auth/signup: Register a new user
        
    * POST /auth/login: Log in an existing user
        

## Defining the Data Models

With the routes defined, we can start thinking about the data models for our database. A data model is a representation of the data that will be stored in the database and the relationships between that data. We'll be using Mongoose to define our schema.

We will have two data models: Blog and User.

### User

| field | data\_type | constraints |
| --- | --- | --- |
| firstname | String | required |
| lastname | String | required |
| email | String | required, unique, index |
| password | String | required |
| articles | Array, \[ObjectId\] | ref - Blog |

### Blog

| field | data\_type | constraints |
| --- | --- | --- |
| title | String | required, unique, index |
| description | String |  |
| tags | Array, \[String\] |  |
| imageUrl | String |  |
| author | ObjectId | ref - Users |
| timestamp | Date |  |
| state | String | required, enum: \['draft', 'published'\], default:'draft' |
| readCount | Number | default:0 |
| readingTime | String |  |
| body | String | required |

Mongoose has a method called `populate()`, which lets you reference documents in other collections. `populate()` will automatically replace the specified paths in the document with document(s) from other collection(s). The `User` model has its `articles` field set to an array of `ObjectId`'s. The `ref` option is what tells Mongoose which model to use during population, in this case the `Blog` model. All `_id`'s we store here must be article `_id`'s from the `Blog` model. Similarly, the `Blog` model references the `User` model in its `author` field.

## Implementing the data models

- In `/src/models`, create a file called `blog.model.js` and set up the Blog model:
   
```javascript
const mongoose = require("mongoose");
const uniqueValidator = require('mongoose-unique-validator');

const { Schema } = mongoose;

const BlogSchema = new Schema({
	title: { type: String, required: true, unique: true, index: true },
	description: String,
	tags: [String],
	author: { type: Schema.Types.ObjectId, ref: "Users" },
	timestamp: Date,
    imageUrl: String,
	state: { type: String, enum: ["draft", "published"], default: "draft" },
	readCount: { type: Number, default: 0 },
	readingTime: String,
	body: { type: String, required: true },
});

// Apply the uniqueValidator plugin to the blog model
BlogSchema.plugin(uniqueValidator);

const Blog = mongoose.model("Blog", BlogSchema);

module.exports = Blog;
```

The `title` field is defined as a required string and must be unique across all documents in the collection. The `description` field is defined as a string, and the `tags` field is defined as an array of strings. The `author` field is defined as a reference to a document in the `users` collection, and the `timestamp` field is defined as a date. The `imageUrl` field is defined as a string, the `state` field is defined as a string with a set of allowed values (either "draft" or "published"), and the `readCount` field is defined as a number with a default value of 0. The `readingTime` field is defined as a string, and the `body` field is defined as a required string.

`mongoose-unique-validator` is a plugin that adds pre-save validation for unique fields within a Mongoose schema. It will validate the `unique` option in the schema and prevent the insertion of a document if the value of a unique field already exists in the collection.

- In `/src/models`, create a file called `user.model.js` and set up the User model:
    

```javascript
const mongoose = require("mongoose");
const uniqueValidator = require("mongoose-unique-validator");
const bcrypt = require("bcrypt");

const { Schema } = mongoose;

const UserModel = new Schema({
	firstname: { type: String, required: true },
	lastname: { type: String, required: true },
	email: {
		type: String,
		required: true,
		unique: true,
		index: true,
	},
	password: { type: String, required: true },
	articles: [{ type: Schema.Types.ObjectId, ref: "Blog" }],
});

// Apply the uniqueValidator plugin to the user model
UserModel.plugin(uniqueValidator);

UserModel.pre("save", async function (next) {
	const user = this;

	if (user.isModified("password") || user.isNew) {
		const hash = await bcrypt.hash(this.password, 10);

		this.password = hash;
	} else {
		return next();
	}
});

const User = mongoose.model("Users", UserModel);

module.exports = User;
```

The `firstname` and `lastname` fields are defined as required strings, and the `email` field is defined as a required string and must be unique across all documents in the collection. The `password` field is defined as a required string, and the `articles` field is defined as an array of references to documents in the `Blog` collection.

The `pre` hook is used to add a function that will be executed before a specific Mongoose method is run. The pre-save hook here hashes the user's password with the npm module `bcrypt`, before the user document is saved to the database.

## Setting Up the Database Connection

Now that we have our routes and data models defined, it's time to set up the database connection.

- Set up your MongoDB database and save the connection url in your `.env` file.
    
- Run the following command to install the npm package `mongoose`:
    

```plaintext
npm install --save mongoose
```

- Create a file called `db.js` in the `/database` directory. In `/database/db.js`, set up the database connection using Mongoose:
    

```javascript
const mongoose = require('mongoose');

const connect = (url) => { 
mongoose.connect(url || 'mongodb://[localhost:27017](http://localhost:27017)')

mongoose.connection.on("connected", () => { 
    console.log("Connected to MongoDB Successfully"); 
});

mongoose.connection.on("error", (err) => { 
    console.log("An error occurred while connecting to MongoDB");
    console.log(err);    
}); 
}

module.exports = { connect };
```

The `connect` function takes an optional `url` argument, which specifies the URL of the database to connect to. If no URL is provided, it defaults to 'mongodb://[localhost:27017](http://localhost:27017)', which connects to the MongoDB instance running on the local machine at the default port (27017).

- Create an `index.js` file in the `/database` directory:
    

```javascript
const database = require("./db");

module.exports = {
  database,
};
```

Now that we have the database connection set up, in the next article, we'll dive into two important concepts- authentication and data validation. Stay tuned!