Tuesday, March 22, 2016

Creating a Node.js REST Service with TypeScript

In my last post I talked about the basics of creating a Node.js server app using TypeScript in Visual Studio. In this post I'll expand upon that to show how to create a RESTful web service that exposes an API to access CRUD operations for a "todo" web app.

Body Parser Middleware


We'll start where we left off in the previous post. In that post we added the Express package using npm. For this app we also need to add the Express body-parser module. This module is a middleware component for Express that parses the body of HTTP requests so we can get data that has been sent via a POST request. To add it right click on "npm" in the project tree, select "Install new npm Packages", then search for "body-parser" and install it.


Now we need the type definition file for body-parser so we get intellisense in our TypeScript files. We'll have to get that from the Node command line using "typings". Make sure you're in the project folder then execute the following:

> typings install body-parser --ambient --save

Now we can start to write our main module, app.ts and include body-parser.

import express = require("express");
let app = express();

import bodyParser = require("body-parser");
app.use(bodyParser.urlencoded({ extended: true }));

First we import express and create our app. Then we import body-parser and tell our app to use its urlencoded middlware component. This middleware takes incoming POST requests where the body uses x-www-form-urlencoded format and parses the form data into name/value pairs which can be accessed through the request.body object. We'll see more about that later.

The Data Model


Before we go any further we need to define our todo item model. So let's create a folder in our project called models and add a new TypeScript file called todoModel.js.

export interface ITodoItem
{
    id: number;
    name: string;
    created: Date;
}

let nextId = 1;

export class TodoItemModel implements ITodoItem
{
    public id: number;
    public created: Date;

    constructor(public name: string)
    {
        this.id = nextId++;
        this.created = new Date();
    }
}

Inside there we define an ITodoItem interface, then we define a TodoItemModel class that implements it. I like to have an interface for each model so that we don't always need to use the class, especially if we're getting objects from JSON.

Notice that the interface and class are exported so we can use them outside of the module. Notice also the variable named nextId. This is used in the constructor to create a unique ID for each todo item that is created. We don't want this variable to be visible outside of the module so we don't export it.

The Data Repository


Now we need a repository to save our todo items in. Let's create a folder called repositories and add a TypeScript file called TodoItemRepo.ts.

import models = require("../models/todoModel");

export interface ITodoItemRepo
{
    add(item: models.ITodoItem): void;
    remove(id: number): models.ITodoItem;
    findById(id: number): models.ITodoItem;
}

export class TodoItemList implements ITodoItemRepo
{
    private list: models.ITodoItem[] = [];

    public add(item: models.ITodoItem): void
    {
        this.list.push(item);
    }

    public remove(id: number): models.ITodoItem
    {
        for (let i = 0; i < this.list.length; i++)
        {
            if (this.list[i].id === id)
            {
                return this.list.splice(i, 1)[0];
            }
        }

        return null;
    }

    public findById(id: number): models.ITodoItem
    {
        for (let i = 0; i < this.list.length; i++)
        {
            if (this.list[i].id === id)
            {
                return this.list[i];
            }
        }
        return null;
    }
}

First we import our todo item model, then define an interface for our todo item repository. We need a way to add, remove and find items in the repository. Then we implement a repository for that interface called TodoItemList that merely stores the items in an array. In a real app our repository would probably interact with a database, but that's beyond the scope of this post.

Defining the Routes


Now that we have our model and a place to store our data we can create our routes. First thing we want to do is create a folder called routes and create a new TypeScript file called todoAPI.ts. This module will hold all of our routes and the implementation of those routes. By placing them in their own file we keep them out of our main file app.ts to keep it clean.

import express = require("express");
import models = require("../models/todoModel");
import repos = require("../repositories/todoItemRepo");

let todoList = new repos.TodoItemList();

export function initRoutes(app: express.Express): void
{
    app.get('/todo', list)
        .post("/todo", create)
        .get("/todo/:id", getById)
        .put("/todo/:id", update)
        .delete("/todo/:id", del);
}

function list(req: express.Request, res: express.Response): void
{
    res.status(200).json(todoList);
}

function create(req: express.Request, res: express.Response): void
{
    todoList.add(new models.TodoItemModel(req.body.name));
    res.status(200).json(todoList);
}

function getById(req: express.Request, res: express.Response): void
{
    let id = parseInt(req.params.id);
    let item = todoList.findById(id);
    if (item)
    {
        res.status(200).json(item);
    }
    else
    {
        res.status(400).send("Item not found: " + req.params.id);
    }
}

function update(req: express.Request, res: express.Response): void
{
    let id = parseInt(req.params.id);
    let item = todoList.findById(id);
    if (item)
    {
        item.name = req.body.name;
        res.status(200).json(item);
    }
    else
    {
        // Bad request
        res.status(400).send("Item not found: " + req.params.id);
    }
}

function del(req: express.Request, res: express.Response): void
{
    let id = parseInt(req.params.id);
    if (todoList.remove(id))
    {
        res.status(200).json(todoList);
    }
    else
    {
        // Bad request
        res.status(400).send("Item not found: " + req.params.id);
    }
}

First we import the model and repository modules. Then we create a new TodoItemList to hold our items. Next is the only function that will be visible outside of the module, initRoutes(). This function takes our app that we created in app.ts and adds the routes for our API to it.

All of the routes start with "/todo" and a few have "/:id" appended. By placing a colon at the beginning of a route part it tells the express router that this is a parameter. All route parameters will be map it to the request.params object. So in this case we should be able to access request.params.id.
Each route is mapped to a function that is defined in the module. Note that none of the handler functions are exported. They don't need to be because we are setting up the routes inside the module.
  • The list() function merely returns the todo list object.
  • The create() function first gets name of the new todo item from the request.body object using request.body.name (The body-parser middleware put it there for us). Then it creates a new instance of a TodoItemModel and adds it to the list. Finally it returns the updated list.
  • The getById() function gets the id parameter from the request.params and returns the matching todo item. If one isn't found it returns a 400 HTTP status.
  • The update() function gets the id of the item to update from the request.params and the new name of the todo item from the request.body object and updates it. Then it returns the item.
  • And finally the del() function gets the id of the item to delete from the request.params and removes the item from the list then sends back the updated list.

Starting the Service


That's it for our todo API! Now we need to go back into our main file, app.ts and tell it to use our routes and start the service. We add the following to that file:

import todoApi = require("./routes/todoApi");
todoApi.initRoutes(app);

let port = 8080;
let server = app.listen(port, () => console.log("Listening on port " + server.address().port));

First we import our todoAPI module, then call its initRoutes() function passing in our application. Then lastly we start the service by calling app.listen().

Let's go run it now and test it out. From the Node command line we start the service.

node app

We should get the message, "Listening on port 8080" if all went well. Now we need to test it.

Testing the API


In order to test our API we'll need a tool to test REST web services. We're not creating the front end as part of this post so we'll need another way to test it. I like to use the Postman extension in Chrome (http://www.getpostman.com/). It allows you to specify any REST verb as well as specify parameters in the body of the request.

First let's add a todo item. To do that we will set the verb to POST and use the URL: http://localhost:8080/todo. We also need to specify the todo item name in the body. We need to use x-www-form-urlencoded as the body type.



Now click the Send button and it adds a new item named Task1. It also returns the todo list which we see in the response body. That means it worked. Now let's test some other routes.

  • Set the verb to GET and call http://localhost:8080/todo. This will hit return the list of all items.
  • Set the verb to GET and call http://localhost:8080/todo/1. This will get the item with the id of 1.
  • Set the verb to PUT, add a name parameter to the request body, and call http://localhost:8080/todo/1. This will change the name of the item with id of 1.
  • Set the verb to DELETE and call http://localhost:8080/todo/1. This will delete the item with id of 1.

In this way you can play around with the todo service API adding, removing and changing todo items.

No we have a working web service with CRUD operations for managing a list of todo items. From here we could write a front end app that consumes these APIs. Look for a future post to implement that.

<jmg/>

No comments:

Post a Comment