Creating REST APIs using Node, Express, Typescript, Mongodb

  1. Setting up Nodejs Express Typescript Server
  2. Setting up a custom logger for node express typescript server
  3. Setup Code Linting in VSCode for Express Typescript Server with Eslint
  4. Configuring Tooling using ESLint, Editorconfig, Prettier and Husky
  5. Setting up React Client from scratch
  6. Configuring Monorepo with yarn workspaces and lerna
  7. Creating REST APIs using Node, Express, Typescript, Mongodb

In the last tutorial, we were able to setup our client using React. In this part, we gonna be focusing on the backend/server building the API’s for our Todo App.

Setting up Mongodb

Let’s install

lerna add 'mongoose' --scope '@fullstackopenjs/server' 
lerna add -D '@types/mongoose' --scope '@fullstackopenjs/server'

This will install the dependencies

In order to use mongodb, we first need to setup our environment variables for db connection:

DB_NAME=fullstackopenjs_db
DB_HOST=localhost
DB_PORT=27017
DB_USER=fullstackopenjs_user
DB_USER_PW=mypass123

We will import this in our env.ts file and export the mongodbConfig:

/**
 * Set mongdb details
 */
const mongodbConfig = {
  name: process.env.DB_NAME,
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  user: process.env.DB_USER,
  password: process.env.DB_USER_PW,
};

export {mongodbConfig};

Let’s now create our mongodb connection, create a new directory db, inside it create index.ts:

db/index.ts

import mongoose from 'mongoose';
import { mongodbConfig } from '../env';
import logger from '../lib';

// Build the Mongodb connection string
// const dbURI = `mongodb://${mongodbConfig.user}:${mongodbConfig.password}@${mongodbConfig.host}:${mongodbConfig.port}/${mongodbConfig.name}`;
const localdbURI = `mongodb://${mongodbConfig.host}:${mongodbConfig.port}/${mongodbConfig.name}`;

const dbOptions = {
  useNewUrlParser: true,
  useCreateIndex: true,
  useUnifiedTopology: true,
  useFindAndModify: false,
  autoIndex: true,
  poolSize: 10, // Maintain up to 10 socket connections
  connectTimeoutMS: 10000, // Give up initial connection after 10 seconds
  socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity
};

logger.debug(localdbURI);

// create the db connection object
mongoose
  .connect(localdbURI, dbOptions)
  .then(() => {
    logger.info('Mongodb connected successfully');
  })
  .catch((e) => {
    logger.info('Mongodb Connection Error');
    logger.error(e);
  });

// Connection events
mongoose.connection.on('connected', () => {
  logger.info(`Mongoose default connection open to ${localdbURI}`);
});

// If the connection throws an error
mongoose.connection.on('error', (err) => {
  logger.error(`Mongoose default connection error${err}`);
});

// When the connection is disconnected
mongoose.connection.on('disconnected', () => {
  logger.info('Mongoose default connection disconnected');
});

// If the Node process ends, close the mongoose connection
process.on('SIGINT', () => {
  mongoose.connection.close(() => {
    logger.info('Mongoose default connection disconnected through app termination');
    process.exit(0);
  });
});

We are using mongoose to connect to our local mongodb also using logger to log for errors, if anything goes wrong.

Let’s import our Db config in app.ts:

import './db';

This will check for mongodb connection automatically, when server is started/restarted.

Creating Todo API’s

We need to create a model using mongoose to define the schema of our todo document.
create a new folder inside src named “model”. Inside model create a todo.ts with the following:

import mongoose from 'mongoose';

interface ITodo {
  title: string;
  description: string;
  status: boolean;
}

interface TodoModelInterface extends mongoose.Model<TodoDoc> {
  build(attr: ITodo): TodoDoc;
}

interface TodoDoc extends mongoose.Document {
  title: string;
  description: string;
  status: boolean;
}

const todoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
  },
  description: {
    type: String,
    required: true,
  },
  status: {
    type: Boolean,
    required: true,
  },
});

// builder to create a new instance of Todo
todoSchema.statics.build = (attr: ITodo) => {
  return new Todo(attr);
};

const Todo = mongoose.model<any, TodoModelInterface>('Todo', todoSchema);

export { Todo };

Let’s now create the controllers for our APIs. Inside src directory, create controllers,

Inside controllers, create todos/index.ts:

import { Request, Response } from 'express';
import { Todo } from '../../model/todo';

// get all todos
const getTodos = async (req: Request, res: Response): Promise<void> => {
  try {
    const todos = await Todo.find({});
    res.status(200).json({ todos });
  } catch (err) {
    throw err;
  }
};

// get single todo
const getSingleTodo = async (req: Request, res: Response): Promise<void> => {
  try {
    const id = req.params.id;
    const singleTodo = await Todo.find({ _id: id });
    res.status(200).json({ singleTodo });
  } catch (err) {
    throw err;
  }
};

// add todo
const addTodo = async (req: Request, res: Response): Promise<void> => {
  try {
    const { title, description, status } = req.body;
    const todo = Todo.build({ title, description, status });
    const newTodo = await todo.save();
    res.status(201).json({ message: 'Todo Added', todo: newTodo });
  } catch (err) {
    throw err;
  }
};


// update todo
const updateTodo = async (req: Request, res: Response): Promise<void> => {
  try {
    const id = req.params.id;
    const body = req.body;
    const updatedTodo = await Todo.findByIdAndUpdate({ _id: id }, body);
    const allTodos = await Todo.find();
    res.status(200).json({ message: 'Todo updated', todo: updatedTodo, todos: allTodos });
  } catch (err) {
    throw err;
  }
};

//delete todo
const deleteTodo = async (req: Request, res: Response): Promise<void> => {
  try {
    const id = req.params.id;
    const deletedTodo = await Todo.findByIdAndRemove({ _id: id });
    const allTodos = await Todo.find();
    res.status(200).json({ message: 'Todo deleted', todo: deletedTodo, todos: allTodos });
  } catch (err) {
    throw err;
  }
};

export { getTodos, getSingleTodo, addTodo, updateTodo, deleteTodo };

Now we are ready to consume our Controllers using routes, Let’s define our routes inside src directory src/routes/todo.ts.

import { Router } from 'express';
import { addTodo, deleteTodo, getSingleTodo, getTodos, updateTodo } from '../controllers/todos';

const router: Router = Router();

router.get('/api/v1/todos', getTodos);

router.get('/api/v1/todos/:id', getSingleTodo);

router.post('/api/v1/todos', addTodo);

router.put('/api/v1/edit-todo/:id', updateTodo);

router.delete('/api/v1/delete-todo/:id', deleteTodo);

export default router;

Great we have now our route’s setup, let’s consume the routes in our app.ts:

import todoRouter from './routes/todo';
.
.
.
    /**
     * use routes
     */
    this.app.use(todoRouter);

This ensures that whenever we hit http://localhost:3001/api/v1/todos using GET, we will get all todos and the rest of the apis.

Great ! we setup our API’s for Todo, that’s it for now ๐Ÿ™‚

Scroll to Top