Building a Simple Todo API with Express and TypeScript

Building a Simple Todo API with Express and TypeScript

Introduction

In this blog, we'll explore how to build a simple Todo API using Express and TypeScript. The project will allow users to create, retrieve, update, and fetch todos while maintaining them in memory using a Map. This is a great way to learn how to handle API requests and responses in a structured way.

Setting Up the Project

Before starting, please make sure you have Node.js and Bun installed as the package manager. You can get Bun from Bun's official website if you haven't installed Bun yet.

  1. Initialize the project:

     bun init
    
  2. Install the necessary dependencies:

     bun add express
    
  3. Install TypeScript and type definitions:

     bun add -d typescript @types/express
    

Creating the Express Server

We start by importing Express and defining the Todo interface and a Map to store todos.

import express from "express";
import { Status } from "./enum";
const app = express();

app.use(express.json());

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

let todos = new Map<string, Todo>();

Function to Store Data

To validate and store a new todo, we create a helper function setData.

function setData(id: string, todo: Todo) {
  if (!todo.title || !todo.description || todo.status === undefined) {
    return false;
  }
  todos.set(id, todo);
  return true;
}

API Endpoints

Root Endpoint

This is a simple endpoint that returns a welcome message.

app.get("/", (_req, res) => {
  res.json({ message: "Hello, World" });
});

Create a Todo

Users can create a new todo by sending a POST request with a title, description, and status.

app.post("/create-todo", (req, res) => {
  const { title, description, status }: Todo = req.body;
  const id: string = crypto.randomUUID();;
  const isAdded: boolean = setData(id, { title, description, status });

  if (!isAdded) {
    res.status(Status.BadRequest).send("error in body");
    return;
  }

  res.status(Status.Ok).send("todo created");
});

Retrieve All Todos

Fetches all stored todos.

app.get("/get-todos", (_req, res) => {
  if (todos.size == 0) {
    res.status(Status.NotFounded).json({ message: "no todos in database" });
    return;
  }
  res.status(Status.Ok).json(Object.fromEntries(todos));
});

Retrieve a Single Todo by ID

Fetches a todo by its ID.

app.get("/get-todo/:id", (req, res) => {
  const id: string = req.params.id;
  const todo = todos.get(id);
  if (!todo) {
    res.status(Status.NotFounded).json({ message: `no todo found with ID: ${id}` });
    return;
  }
  res.status(Status.Ok).json(todo);
});

Update a Todo's Status

Users can update a todo's status using a PATCH request.

app.patch("/update-todo/:id", (req, res) => {
  const id = req.params.id;
  const { status }: { status: boolean } = req.body;

  let todo = todos.get(id);
  if (!todo) {
    res.status(Status.NotFounded).json({ message: "todo not found" });
    return;
  }

  todo.status = status;
  todos.set(id, todo);
  res.status(Status.Ok).json({ message: "todo updated", todo });
});

Status Enum

For better readability and maintainability, we define an enum for HTTP status codes.

export enum Status {
  Ok = 200,
  NoContent = 204,
  BadRequest = 400,
  NotFounded = 404,
}

Running the Server

Start the server using:

bun run index.ts

You should see:

server is serving on http://localhost:3000

Conclusion

This simple Todo API demonstrates how to create and manage an Express server with TypeScript. You can extend it by integrating a database like MongoDB or PostgreSQL and adding user authentication for a full-fledged application. Happy coding!