Create AngularJS RESTful Heroes API with TypeScript, Node.js and Express.js

Shahriar Shovon

Supported OS

  • All

Categories

Share this post with your friends

In this post, I will create the heroes HTTP REST API provided by AngularJS tutorial on AngularJS official documentation. They used InMemoryWebApi to simulate these API calls. So I thought of implementing it just for fun. In my earlier post, I created the REST API with Node.js and Express. In this tutorial I will do the same thing with TypeScript. So that you can compare these 2 implementations. Let's get started with the API. Want to do the same thing with just JavaScript? Check out my earlier post at Create AngularJS RESTful HTTP Heroes API with Node.js and Express.js

Github Repository

If you just want to quickly test this API, you can just clone my git repo. git clone https://github.com/shovon8/angular-hero-api-ts.git cd angular-hero-api-ts/ npm install -g typescript npm install npm start

API Endpoints

In this API project, I will have the following API endpoints. GET /api/heroes Returns all the heroes data GET /api/hero/:id Returns hero that has an id of :id PUT /api/heroes Creates a new hero POST /api/hero/:id Update an existing hero DELETE /api/hero/:id Delete a hero that has an id of :id

Initialization

First create a directory for the API project. I am gonna call it heroes-ts Then, we have to create a package.json file. Open your terminal and create one with the following command. npm init It should ask you some questions, Fill in the details any way you like. If you're lazy to do so, you can use mine package.json file. { "name": "angularjs-hero-service", "version": "1.0.0", "description": "", "main": "server.js", "scripts": { "start": "node out/server.js" }, "author": "Shahriar Shovon", "license": "ISC", "dependencies": { "body-parser": "^1.17.2", "express": "^4.15.4" }, "devDependencies": { "@types/body-parser": "^1.16.5", "@types/express": "^4.0.37", "@types/node": "^8.0.24" } }

Installing TypeScript

Installing TypeScript is an easy task. All you have to do is run the following command and you're good to go. npm -g install typescript On Linux, you must add sudo before. sudo npm -g install typescript

Installing the Dependencies

For this project, we are going to use express and body-parser packages of node.js We can install express and body-parser with the following command. npm install --save express npm install --save body-parser If you're using my package.json file, then you can run npm install to install the requires packages.

Create TypeScript compiler configuration file

Now we should create a tsconfig.json file in our project root heroes-ts/ Doing so will make compiling typescript to javascript easier. Just copy and paste the codes below. { "compilerOptions": { "target": "es5", "rootDir": "./src", "outDir": "./out", "moduleResolution": "node", "module": "commonjs" } }

Creating the Folder Structure

Inside the project directory heroes-ts, create another directory src/ and out/ In the src/ directory, I will keep the typescript files and in the out/ directory all the transpiled typescript(javascript) files will be saved. I want to make my app modular, so I created a src/routes folder in my project root. I also created a src/models directory. In this directory, I will store all the files related to handle the data requests. I used an array based implementation in this post. but this app can be easily configured to work with mongodb, mysql, sqlite or any other database. All you have to do is write another model file similar to the one I am creating with same function name. Then only changing the module I loaded with your own will leave the app working as expected. No other changes required. I stored the heroes array in the src/data directory. So my current folder structure is - heroes-ts/ - src/ - data/ - models/ - routes/ - out/ - package.json - tsconfig.json

Adding the Data

As mentioned earlier, in this project, I will use an array to store the heroes data. So create a file hero.data.ts in src/data/ directory and add the following code. export let HeroData = [ { id: 11, name: 'Mr. Nice' }, { id: 12, name: 'Narco' }, { id: 13, name: 'Bombasto' }, { id: 14, name: 'Celeritas' }, { id: 15, name: 'Magneta' }, { id: 16, name: 'RubberMan' }, { id: 17, name: 'Dynama' }, { id: 18, name: 'Dr IQ' }, { id: 19, name: 'Magma' }, { id: 20, name: 'Tornado' } ]; export interface Hero { id: number; name: string; } These are the hero data directly copied from AngularJS: Master/Detail. This file exports all the hero data as an array of objects. All we have to do is import it in our Hero model.

Creating the Hero Model

Now I am gonna create hero.model.ts file in src/models/ directory. Add the following code to this file and I will explain what I did afterward. import {Hero, HeroData} from "../data/hero.data"; export class HeroModel { public static getHeroes(): Hero[] { return HeroData; } public static getHero(id): Hero { let hero: Hero[] = HeroData.filter((h) => h.id === parseInt(id)); if(hero.length > 0) { return hero[0]; } else { return null; } } private static getIndexById(id): number { let heroIndex = -1; let heroId = parseInt(id); HeroData.filter((h, i) => { if(h.id === heroId) { heroIndex = i; return true; } return false; }); return heroIndex; } public static update(id, name): Hero { let heroIndex = this.getIndexById(id); if(heroIndex !== -1) { HeroData[heroIndex].name = name; return HeroData[heroIndex]; } else { return null; } } public static create(name): Hero { let hero = { id: HeroData[HeroData.length - 1].id + 1, name: name }; HeroData.push(hero); return hero; } public static delete(id): Hero { let heroIndex = this.getIndexById(id); if(heroIndex !== -1) { let hero = HeroData[heroIndex]; HeroData.splice(heroIndex, 1); return hero; } else { return null; } } } First I imported the HeroData from the src/data/hero.data.ts file. In src/model/hero.model.ts file I have defined a method for each API operation. I added a helper method getIndexById() which takes an id as input and returns the index number of the hero in the HeroData Array. getHeroes() simply returns all the heroes in the HeroData Array. getHero() returns the hero with specific id. Note that, I used filter method of HeroData to find the hero we are looking for. create() uses push() method of Array to push a new hero to the HeroData Array. update() uses getIndexById() to find the index of the HeroData Array, and updates the array at that index. delete() works the same way as update. Instead of updating the document, delete() uses the splice() Array method to remove the element from the Array. I exported HeroModel class using export keyword of typescript, so that I can import it in other files.

Creating the App Sever

Now I am gonna create a src/server.class.ts file on my project root directory heroes-ts/src/ This file will loads everything, define methods to mount the API routes/endpoints and start the server. import * as Express from 'express'; import * as BodyParser from 'body-parser'; export class Server { private app: Express.Application; private port: number; constructor(port: number) { this.port = port; this.app = Express(); this.app.use(BodyParser.urlencoded({extended: false})); } start(): void { this.app.listen(this.port, () => { console.log('server started on port ' + this.port); }); } getPort(): number { return this.port; } addRoute(mountPoint: string, router: Express.Router) { this.app.use(mountPoint, router); } } server.class.ts imports express and body-parser and initializes express with body-parser with app.use(bodyParser.urlencoded({extended: false})) body-parser is used so that we can parse the HTTP GET/POST/PUT/DELETE data from express Now create a new file heroes-ts/src/server.ts and add the following code. import {Server} from './server.class'; import {HeroRouter} from "./routes/hero.routes"; let app = new Server(4242); app.addRoute('/api', HeroRouter); app.start(); This file just imports the Server class, starts the server and mounts the routes in http://localhost:4242/api I also did one more thing here. I wrote the API routes in a different file src/routes/heroe.routes.ts, so I can keep everything nice and tidy in a modular way.

Defining the Routes

Everything is complete. Now all that's left is, the API routes definition. It's really self explanatory, so I am not gonna talk about it. The contents of the src/routes/hero.routes.ts is given bellow. import * as Express from 'express'; import {HeroModel} from "../models/hero.model"; export let HeroRouter = Express.Router(); HeroRouter.get('/heroes', (req, res) => { res.json(HeroModel.getHeroes()); }); HeroRouter.get('/hero/:id', (req, res) => { res.json(HeroModel.getHero(req.params.id) || {msg: 'hero not found'}); }); HeroRouter.post('/hero/:id', (req, res) => { res.json(HeroModel.update(req.params.id, req.body.name) || {msg: 'hero not found'}); }); HeroRouter.put('/heroes', (req, res) => { res.json(HeroModel.create(req.body.name)); }); HeroRouter.delete('/hero/:id', (req, res) => { res.json(HeroModel.delete(req.params.id) || {msg: 'hero not found'}); });

Final Project Structure

The final project structure looks like this - heroes-ts/ - src/ - data/ - hero.data.ts - models/ - hero.model.ts - routes/ - hero.routes.ts server.class.ts server.ts - out/ - package.json - tsconfig.json

Starting the API server

Everything is complete at this point. Now we can start our server and start testing the API. To start the server, first we have to compile the typescript files. To do that, run the following command from the projects root directory. tsc Then simply run npm start if you've used my package.json file or run node out/server.js if you're using your own and haven't configured anything in your package.json file.

Testing the API

You can use curl command line utility or Postman or anything to test these API calls. I am gonna use curl because it's easier to demonstrate without any illustration.

Testing GET /api/heroes

Get all heroes data curl http://localhost:4242/api/heroes [{"id":11,"name":"Mr. Nice"},{"id":12,"name":"Narco"},{"id":13,"name":"Bombasto"},{"id":14,"name":"Celeritas"},{"id":15,"name":"Magneta"},{"id":16,"name":" RubberMan"},{"id":17,"name":"Dynama"},{"id":18,"name":"Dr IQ"},{"id":19,"name":"Magma"},{"id":20,"name":"Tornado"}]

Testing GET /api/hero/:id

Get hero with id 12 curl http://localhost:4242/api/hero/12 {"id":12,"name":"Narco"} Get hero with id 15 curl http://localhost:4242/api/hero/15 {"id":15,"name":"Magneta"}

Testing PUT /api/heroes

Let's create a new hero Pika curl -X PUT -d 'name=Pika' http://localhost:4242/api/heroes {"id":21,"name":"Pika"} curl http://localhost:4242/api/heroes [{"id":11,"name":"Mr. Nice"},{"id":12,"name":"Narco"},{"id":13,"name":"Bombasto"},{"id":14,"name":"Celeritas"},{"id":15,"name":"Magneta"},{"id":16,"name":" RubberMan"},{"id":17,"name":"Dynama"},{"id":18,"name":"Dr IQ"},{"id":19,"name":"Magma"},{"id":20,"name":"Tornado"},{"id":21,"name":"Pika"}] You can see that the new hero Pika is added to the list.

Testing POST /api/hero/:id

Let's update our newly added hero Pika to Pikachu curl -X POST -d 'name=Pikachu' http://localhost:4242/api/hero/21 {"id":21,"name":"Pikachu"} curl http://localhost:4242/api/heroes [{"id":11,"name":"Mr. Nice"},{"id":12,"name":"Narco"},{"id":13,"name":"Bombasto"},{"id":14,"name":"Celeritas"},{"id":15,"name":"Magneta"},{"id":16,"name":" RubberMan"},{"id":17,"name":"Dynama"},{"id":18,"name":"Dr IQ"},{"id":19,"name":"Magma"},{"id":20,"name":"Tornado"},{"id":21,"name":"Pikachu"}] You can see that the hero with id 21 is updated.

Testing DELETE /api/hero/:id

Let's delete our newly created hero Pikachu with id 21 curl -X DELETE http://localhost:4242/api/hero/21 {"msg":"success"} curl http://localhost:4242/api/heroes [{"id":11,"name":"Mr. Nice"},{"id":12,"name":"Narco"},{"id":13,"name":"Bombasto"},{"id":14,"name":"Celeritas"},{"id":15,"name":"Magneta"},{"id":16,"name":" RubberMan"},{"id":17,"name":"Dynama"},{"id":18,"name":"Dr IQ"},{"id":19,"name":"Magma"},{"id":20,"name":"Tornado"}] You can see that Pikachu is not in the list anymore. So that's it for this post. Check this link if you want to do the same thing with just Javascript. Thank you for visiting.