├── .gitignore ├── .jshintrc ├── README.md ├── dist ├── App.js ├── data.json ├── index.js ├── routes │ └── HeroRouter.js ├── src │ ├── App.js │ ├── index.js │ └── routes │ │ └── HeroRouter.js └── test │ ├── helloWorld.test.js │ └── hero.test.js ├── gulpfile.js ├── package.json ├── src ├── App.ts ├── data.json ├── index.ts └── routes │ └── HeroRouter.ts ├── test ├── helloWorld.test.ts └── hero.test.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "esnext": true, 4 | "jasmine": false, 5 | "spyOn": false, 6 | "it": false, 7 | "console": false, 8 | "describe": false, 9 | "expect": false, 10 | "beforeEach": false, 11 | "afterEach": false, 12 | "waits": false, 13 | "waitsFor": false, 14 | "runs": false, 15 | "$": false, 16 | "confirm": false 17 | }, 18 | "esnext": true, 19 | "node" : true, 20 | "browser" : true, 21 | "boss" : false, 22 | "curly": false, 23 | "debug": false, 24 | "devel": false, 25 | "eqeqeq": true, 26 | "evil": true, 27 | "forin": false, 28 | "immed": true, 29 | "laxbreak": false, 30 | "newcap": true, 31 | "noarg": true, 32 | "noempty": false, 33 | "nonew": false, 34 | "nomen": false, 35 | "onevar": true, 36 | "plusplus": false, 37 | "regexp": false, 38 | "undef": true, 39 | "sub": true, 40 | "strict": false, 41 | "white": true, 42 | "unused": false 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Developing a RESTful API With Node and TypeScript 2 | 3 | ## Want to learn how to build this project? 4 | 5 | Check out the [blog post](http://mherman.org/blog/2016/11/05/developing-a-restful-api-with-node-and-typescript/#.WB3zyeErJE4). 6 | 7 | ## Want to use this project? 8 | 9 | 1. Fork/Clone 10 | 1. Install dependencies - `npm install` 11 | 1. Compile - `npm run build` 12 | 1. Compile assets - `gulp assets` 13 | 1. Run the development server - `npm start` 14 | 1. Test - `npm test` 15 | 16 | ## Sample Projects 17 | 18 | - [Simple whois REST API](https://github.com/wingsuitist/whoissv) 19 | -------------------------------------------------------------------------------- /dist/App.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const express = require('express'); 3 | const logger = require('morgan'); 4 | const bodyParser = require('body-parser'); 5 | const HeroRouter_1 = require('./routes/HeroRouter'); 6 | // Creates and configures an ExpressJS web server. 7 | class App { 8 | //Run configuration methods on the Express instance. 9 | constructor() { 10 | this.express = express(); 11 | this.middleware(); 12 | this.routes(); 13 | } 14 | // Configure Express middleware. 15 | middleware() { 16 | this.express.use(logger('dev')); 17 | this.express.use(bodyParser.json()); 18 | this.express.use(bodyParser.urlencoded({ extended: false })); 19 | } 20 | // Configure API endpoints. 21 | routes() { 22 | /* This is just to get up and running, and to make sure what we've got is 23 | * working so far. This function will change when we start to add more 24 | * API endpoints */ 25 | let router = express.Router(); 26 | // placeholder route handler 27 | router.get('/', (req, res, next) => { 28 | res.json({ 29 | message: 'Hello World!' 30 | }); 31 | }); 32 | this.express.use('/', router); 33 | this.express.use('/api/v1/heroes', HeroRouter_1.default); 34 | } 35 | } 36 | Object.defineProperty(exports, "__esModule", { value: true }); 37 | exports.default = new App().express; 38 | -------------------------------------------------------------------------------- /dist/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Luke Cage", 5 | "aliases": ["Carl Lucas", "Power Man", "Mr. Bulletproof", "Hero for Hire"], 6 | "occupation": "bartender", 7 | "gender": "male", 8 | "height": { 9 | "ft": 6, 10 | "in": 3 11 | }, 12 | "hair": "bald", 13 | "eyes": "brown", 14 | "powers": [ 15 | "strength", 16 | "durability", 17 | "healing" 18 | ] 19 | }, 20 | { 21 | "id": 2, 22 | "name": "Spider-Man", 23 | "aliases": ["Dr. Peter Benjamin Parker", "Spidey", "Web-Sligner", "Spider-X-Man"], 24 | "occupation": "scientist", 25 | "gender": "male", 26 | "height": { 27 | "ft": 5, 28 | "in": 10 29 | }, 30 | "hair": "brown", 31 | "eyes": "hazel", 32 | "powers": [ 33 | "wall-crawling", 34 | "strength", 35 | "speed", 36 | "stamina", 37 | "durability", 38 | "agility", 39 | "healing", 40 | "reflexes", 41 | "Spider-Sense", 42 | "genius" 43 | ] 44 | }, 45 | { 46 | "id": 3, 47 | "name": "Captain America", 48 | "aliases": [ 49 | "Winghead", 50 | "Shield-Slinger", 51 | "the Captain", 52 | "Cap", 53 | "Yeoman America", 54 | "Sentinel of Liberty", 55 | "The Living Legend" 56 | ], 57 | "occupation": "special agent", 58 | "gender": "male", 59 | "height": { 60 | "ft": 6, 61 | "in": 2 62 | }, 63 | "hair": "blonde", 64 | "eyes": "blue", 65 | "powers": [ 66 | "strength", 67 | "speed", 68 | "durability", 69 | "agility", 70 | "reflexes", 71 | "stamina", 72 | "healing", 73 | "longevity" 74 | ] 75 | }, 76 | { 77 | "id": 4, 78 | "name": "Iron Man", 79 | "aliases": [ 80 | "Tony Stark", 81 | "Golden Gladiator", 82 | "Spare Parts Man", 83 | "Space-Knight" 84 | ], 85 | "occupation": "inventor", 86 | "gender": "male", 87 | "height": { 88 | "ft": 6, 89 | "in": 1 90 | }, 91 | "hair": "black", 92 | "eyes": "blue", 93 | "powers": [] 94 | }, 95 | { 96 | "id": 5, 97 | "name": "Wolverine", 98 | "aliases": [ 99 | "Logan", 100 | "Weapon X", 101 | "Death", 102 | "Agent Ten", 103 | "Fist of Legend" 104 | ], 105 | "occupation": "special agent", 106 | "gender": "male", 107 | "height": { 108 | "ft": 5, 109 | "in": 3 110 | }, 111 | "hair": "black", 112 | "eyes": "blue", 113 | "powers": [ 114 | "healing", 115 | "acute senses", 116 | "strength", 117 | "speed", 118 | "durability", 119 | "agility", 120 | "stamina", 121 | "weather adaptation", 122 | "animal empathy", 123 | "bone claws" 124 | ] 125 | } 126 | ] 127 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const http = require('http'); 3 | const debug = require('debug'); 4 | const App_1 = require('./App'); 5 | debug('ts-express:server'); 6 | const port = normalizePort(process.env.PORT || 3000); 7 | App_1.default.set('port', port); 8 | const server = http.createServer(App_1.default); 9 | server.listen(port); 10 | server.on('error', onError); 11 | server.on('listening', onListening); 12 | function normalizePort(val) { 13 | let port = (typeof val === 'string') ? parseInt(val, 10) : val; 14 | if (isNaN(port)) 15 | return val; 16 | else if (port >= 0) 17 | return port; 18 | else 19 | return false; 20 | } 21 | function onError(error) { 22 | if (error.syscall !== 'listen') 23 | throw error; 24 | let bind = (typeof port === 'string') ? 'Pipe ' + port : 'Port ' + port; 25 | switch (error.code) { 26 | case 'EACCES': 27 | console.error(`${bind} requires elevated privileges`); 28 | process.exit(1); 29 | break; 30 | case 'EADDRINUSE': 31 | console.error(`${bind} is already in use`); 32 | process.exit(1); 33 | break; 34 | default: 35 | throw error; 36 | } 37 | } 38 | function onListening() { 39 | let addr = server.address(); 40 | let bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`; 41 | debug(`Listening on ${bind}`); 42 | } 43 | -------------------------------------------------------------------------------- /dist/routes/HeroRouter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const express_1 = require('express'); 3 | const Heroes = require('../data'); 4 | class HeroRouter { 5 | /** 6 | * Initialize the HeroRouter 7 | */ 8 | constructor() { 9 | this.router = express_1.Router(); 10 | this.init(); 11 | } 12 | /** 13 | * GET all Heroes. 14 | */ 15 | getAll(req, res, next) { 16 | res.send(Heroes); 17 | } 18 | /** 19 | * GET one hero by id 20 | */ 21 | getOne(req, res, next) { 22 | let query = parseInt(req.params.id); 23 | let hero = Heroes.find(hero => hero.id === query); 24 | if (hero) { 25 | res.status(200) 26 | .send({ 27 | message: 'Success', 28 | status: res.status, 29 | hero 30 | }); 31 | } 32 | else { 33 | res.status(404) 34 | .send({ 35 | message: 'No hero found with the given id.', 36 | status: res.status 37 | }); 38 | } 39 | } 40 | /** 41 | * Take each handler, and attach to one of the Express.Router's 42 | * endpoints. 43 | */ 44 | init() { 45 | this.router.get('/', this.getAll); 46 | this.router.get('/:id', this.getOne); 47 | } 48 | } 49 | exports.HeroRouter = HeroRouter; 50 | // Create the HeroRouter, and export its configured Express.Router 51 | const heroRoutes = new HeroRouter(); 52 | heroRoutes.init(); 53 | Object.defineProperty(exports, "__esModule", { value: true }); 54 | exports.default = heroRoutes.router; 55 | -------------------------------------------------------------------------------- /dist/src/App.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const express = require("express"); 3 | const logger = require("morgan"); 4 | const bodyParser = require("body-parser"); 5 | const HeroRouter_1 = require("./routes/HeroRouter"); 6 | class App { 7 | constructor() { 8 | this.express = express(); 9 | this.middleware(); 10 | this.routes(); 11 | } 12 | middleware() { 13 | this.express.use(logger('dev')); 14 | this.express.use(bodyParser.json()); 15 | this.express.use(bodyParser.urlencoded({ extended: false })); 16 | } 17 | routes() { 18 | let router = express.Router(); 19 | router.get('/', (req, res, next) => { 20 | res.json({ 21 | message: 'Hello World!' 22 | }); 23 | }); 24 | this.express.use('/', router); 25 | this.express.use('/api/v1/heroes', HeroRouter_1.default); 26 | } 27 | } 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | exports.default = new App().express; 30 | -------------------------------------------------------------------------------- /dist/src/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const http = require("http"); 3 | const debug = require("debug"); 4 | const App_1 = require("./App"); 5 | debug('ts-express:server'); 6 | const port = normalizePort(process.env.PORT || 3000); 7 | App_1.default.set('port', port); 8 | const server = http.createServer(App_1.default); 9 | server.listen(port); 10 | server.on('error', onError); 11 | server.on('listening', onListening); 12 | function normalizePort(val) { 13 | let port = (typeof val === 'string') ? parseInt(val, 10) : val; 14 | if (isNaN(port)) 15 | return val; 16 | else if (port >= 0) 17 | return port; 18 | else 19 | return false; 20 | } 21 | function onError(error) { 22 | if (error.syscall !== 'listen') 23 | throw error; 24 | let bind = (typeof port === 'string') ? 'Pipe ' + port : 'Port ' + port; 25 | switch (error.code) { 26 | case 'EACCES': 27 | console.error(`${bind} requires elevated privileges`); 28 | process.exit(1); 29 | break; 30 | case 'EADDRINUSE': 31 | console.error(`${bind} is already in use`); 32 | process.exit(1); 33 | break; 34 | default: 35 | throw error; 36 | } 37 | } 38 | function onListening() { 39 | let addr = server.address(); 40 | let bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`; 41 | debug(`Listening on ${bind}`); 42 | } 43 | -------------------------------------------------------------------------------- /dist/src/routes/HeroRouter.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const express_1 = require("express"); 3 | const Heroes = require('../data'); 4 | class HeroRouter { 5 | constructor() { 6 | this.router = express_1.Router(); 7 | this.init(); 8 | } 9 | getAll(req, res, next) { 10 | res.send(Heroes); 11 | } 12 | getOne(req, res, next) { 13 | let query = parseInt(req.params.id); 14 | let hero = Heroes.find(hero => hero.id === query); 15 | if (hero) { 16 | res.status(200) 17 | .send({ 18 | message: 'Success', 19 | status: res.status, 20 | hero 21 | }); 22 | } 23 | else { 24 | res.status(404) 25 | .send({ 26 | message: 'No hero found with the given id.', 27 | status: res.status 28 | }); 29 | } 30 | } 31 | init() { 32 | this.router.get('/', this.getAll); 33 | this.router.get('/:id', this.getOne); 34 | } 35 | } 36 | exports.HeroRouter = HeroRouter; 37 | const heroRoutes = new HeroRouter(); 38 | heroRoutes.init(); 39 | Object.defineProperty(exports, "__esModule", { value: true }); 40 | exports.default = heroRoutes.router; 41 | -------------------------------------------------------------------------------- /dist/test/helloWorld.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const chai = require("chai"); 3 | const chaiHttp = require("chai-http"); 4 | const App_1 = require("../src/App"); 5 | chai.use(chaiHttp); 6 | const expect = chai.expect; 7 | describe('baseRoute', () => { 8 | it('should be json', () => { 9 | return chai.request(App_1.default).get('/') 10 | .then(res => { 11 | expect(res.type).to.eql('application/json'); 12 | }); 13 | }); 14 | it('should have a message prop', () => { 15 | return chai.request(App_1.default).get('/') 16 | .then(res => { 17 | expect(res.body.message).to.eql('Hello World!'); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /dist/test/hero.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const chai = require("chai"); 3 | const chaiHttp = require("chai-http"); 4 | const App_1 = require("../src/App"); 5 | chai.use(chaiHttp); 6 | const expect = chai.expect; 7 | describe('GET api/v1/heroes', () => { 8 | it('responds with JSON array', () => { 9 | return chai.request(App_1.default).get('/api/v1/heroes') 10 | .then(res => { 11 | expect(res.status).to.equal(200); 12 | expect(res).to.be.json; 13 | expect(res.body).to.be.an('array'); 14 | expect(res.body).to.have.length(5); 15 | }); 16 | }); 17 | it('should include Wolverine', () => { 18 | return chai.request(App_1.default).get('/api/v1/heroes') 19 | .then(res => { 20 | let Wolverine = res.body.find(hero => hero.name === 'Wolverine'); 21 | expect(Wolverine).to.exist; 22 | expect(Wolverine).to.have.all.keys([ 23 | 'id', 24 | 'name', 25 | 'aliases', 26 | 'occupation', 27 | 'gender', 28 | 'height', 29 | 'hair', 30 | 'eyes', 31 | 'powers' 32 | ]); 33 | }); 34 | }); 35 | describe('GET api/v1/heroes/:id', () => { 36 | it('responds with single JSON object', () => { 37 | return chai.request(App_1.default).get('/api/v1/heroes/1') 38 | .then(res => { 39 | expect(res.status).to.equal(200); 40 | expect(res).to.be.json; 41 | expect(res.body).to.be.an('object'); 42 | }); 43 | }); 44 | it('should return Luke Cage', () => { 45 | return chai.request(App_1.default).get('/api/v1/heroes/1') 46 | .then(res => { 47 | expect(res.body.hero.name).to.equal('Luke Cage'); 48 | }); 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const ts = require('gulp-typescript'); 3 | const JSON_FILES = ['src/*.json', 'src/**/*.json']; 4 | 5 | // pull in the project TypeScript config 6 | const tsProject = ts.createProject('tsconfig.json'); 7 | 8 | gulp.task('scripts', () => { 9 | const tsResult = tsProject.src() 10 | .pipe(tsProject()); 11 | return tsResult.js.pipe(gulp.dest('dist')); 12 | }); 13 | 14 | gulp.task('watch', ['scripts'], () => { 15 | gulp.watch('src/**/*.ts', ['scripts']); 16 | }); 17 | 18 | gulp.task('assets', function() { 19 | return gulp.src(JSON_FILES) 20 | .pipe(gulp.dest('dist')); 21 | }); 22 | 23 | gulp.task('default', ['watch', 'assets']); 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-node-api", 3 | "version": "1.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node dist/index.js", 8 | "test": "mocha --reporter spec --compilers ts:ts-node/register 'test/**/*.test.ts'", 9 | "build": "gulp scripts" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "devDependencies": { 15 | "@types/body-parser": "0.0.33", 16 | "@types/chai": "^3.4.34", 17 | "@types/chai-http": "0.0.29", 18 | "@types/debug": "0.0.29", 19 | "@types/express": "^4.0.33", 20 | "@types/mocha": "^2.2.32", 21 | "@types/morgan": "^1.7.32", 22 | "@types/node": "^6.0.46", 23 | "chai": "^3.5.0", 24 | "chai-http": "^3.0.0", 25 | "gulp": "^3.9.1", 26 | "gulp-typescript": "^3.1.1", 27 | "mocha": "^3.1.2", 28 | "ts-node": "^3.3.0", 29 | "typescript": "^2.0.6" 30 | }, 31 | "dependencies": { 32 | "body-parser": "^1.15.2", 33 | "debug": "^2.2.0", 34 | "express": "^4.14.0", 35 | "morgan": "^1.7.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/App.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as express from 'express'; 3 | import * as logger from 'morgan'; 4 | import * as bodyParser from 'body-parser'; 5 | 6 | import HeroRouter from './routes/HeroRouter'; 7 | 8 | // Creates and configures an ExpressJS web server. 9 | class App { 10 | 11 | // ref to Express instance 12 | public express: express.Application; 13 | 14 | //Run configuration methods on the Express instance. 15 | constructor() { 16 | this.express = express(); 17 | this.middleware(); 18 | this.routes(); 19 | } 20 | 21 | // Configure Express middleware. 22 | private middleware(): void { 23 | this.express.use(logger('dev')); 24 | this.express.use(bodyParser.json()); 25 | this.express.use(bodyParser.urlencoded({ extended: false })); 26 | } 27 | 28 | // Configure API endpoints. 29 | private routes(): void { 30 | /* This is just to get up and running, and to make sure what we've got is 31 | * working so far. This function will change when we start to add more 32 | * API endpoints */ 33 | let router = express.Router(); 34 | // placeholder route handler 35 | router.get('/', (req, res, next) => { 36 | res.json({ 37 | message: 'Hello World!' 38 | }); 39 | }); 40 | this.express.use('/', router); 41 | this.express.use('/api/v1/heroes', HeroRouter); 42 | } 43 | 44 | } 45 | 46 | export default new App().express; 47 | -------------------------------------------------------------------------------- /src/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Luke Cage", 5 | "aliases": ["Carl Lucas", "Power Man", "Mr. Bulletproof", "Hero for Hire"], 6 | "occupation": "bartender", 7 | "gender": "male", 8 | "height": { 9 | "ft": 6, 10 | "in": 3 11 | }, 12 | "hair": "bald", 13 | "eyes": "brown", 14 | "powers": [ 15 | "strength", 16 | "durability", 17 | "healing" 18 | ] 19 | }, 20 | { 21 | "id": 2, 22 | "name": "Spider-Man", 23 | "aliases": ["Dr. Peter Benjamin Parker", "Spidey", "Web-Sligner", "Spider-X-Man"], 24 | "occupation": "scientist", 25 | "gender": "male", 26 | "height": { 27 | "ft": 5, 28 | "in": 10 29 | }, 30 | "hair": "brown", 31 | "eyes": "hazel", 32 | "powers": [ 33 | "wall-crawling", 34 | "strength", 35 | "speed", 36 | "stamina", 37 | "durability", 38 | "agility", 39 | "healing", 40 | "reflexes", 41 | "Spider-Sense", 42 | "genius" 43 | ] 44 | }, 45 | { 46 | "id": 3, 47 | "name": "Captain America", 48 | "aliases": [ 49 | "Winghead", 50 | "Shield-Slinger", 51 | "the Captain", 52 | "Cap", 53 | "Yeoman America", 54 | "Sentinel of Liberty", 55 | "The Living Legend" 56 | ], 57 | "occupation": "special agent", 58 | "gender": "male", 59 | "height": { 60 | "ft": 6, 61 | "in": 2 62 | }, 63 | "hair": "blonde", 64 | "eyes": "blue", 65 | "powers": [ 66 | "strength", 67 | "speed", 68 | "durability", 69 | "agility", 70 | "reflexes", 71 | "stamina", 72 | "healing", 73 | "longevity" 74 | ] 75 | }, 76 | { 77 | "id": 4, 78 | "name": "Iron Man", 79 | "aliases": [ 80 | "Tony Stark", 81 | "Golden Gladiator", 82 | "Spare Parts Man", 83 | "Space-Knight" 84 | ], 85 | "occupation": "inventor", 86 | "gender": "male", 87 | "height": { 88 | "ft": 6, 89 | "in": 1 90 | }, 91 | "hair": "black", 92 | "eyes": "blue", 93 | "powers": [] 94 | }, 95 | { 96 | "id": 5, 97 | "name": "Wolverine", 98 | "aliases": [ 99 | "Logan", 100 | "Weapon X", 101 | "Death", 102 | "Agent Ten", 103 | "Fist of Legend" 104 | ], 105 | "occupation": "special agent", 106 | "gender": "male", 107 | "height": { 108 | "ft": 5, 109 | "in": 3 110 | }, 111 | "hair": "black", 112 | "eyes": "blue", 113 | "powers": [ 114 | "healing", 115 | "acute senses", 116 | "strength", 117 | "speed", 118 | "durability", 119 | "agility", 120 | "stamina", 121 | "weather adaptation", 122 | "animal empathy", 123 | "bone claws" 124 | ] 125 | } 126 | ] 127 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as http from 'http'; 2 | import * as debug from 'debug'; 3 | 4 | import App from './App'; 5 | 6 | debug('ts-express:server'); 7 | 8 | const port = normalizePort(process.env.PORT || 3000); 9 | App.set('port', port); 10 | 11 | const server = http.createServer(App); 12 | server.listen(port); 13 | server.on('error', onError); 14 | server.on('listening', onListening); 15 | 16 | function normalizePort(val: number|string): number|string|boolean { 17 | let port: number = (typeof val === 'string') ? parseInt(val, 10) : val; 18 | if (isNaN(port)) return val; 19 | else if (port >= 0) return port; 20 | else return false; 21 | } 22 | 23 | function onError(error: NodeJS.ErrnoException): void { 24 | if (error.syscall !== 'listen') throw error; 25 | let bind = (typeof port === 'string') ? 'Pipe ' + port : 'Port ' + port; 26 | switch(error.code) { 27 | case 'EACCES': 28 | console.error(`${bind} requires elevated privileges`); 29 | process.exit(1); 30 | break; 31 | case 'EADDRINUSE': 32 | console.error(`${bind} is already in use`); 33 | process.exit(1); 34 | break; 35 | default: 36 | throw error; 37 | } 38 | } 39 | 40 | function onListening(): void { 41 | let addr = server.address(); 42 | let bind = (typeof addr === 'string') ? `pipe ${addr}` : `port ${addr.port}`; 43 | debug(`Listening on ${bind}`); 44 | } 45 | -------------------------------------------------------------------------------- /src/routes/HeroRouter.ts: -------------------------------------------------------------------------------- 1 | import {Router, Request, Response, NextFunction} from 'express'; 2 | const Heroes = require('../data'); 3 | 4 | export class HeroRouter { 5 | router: Router 6 | 7 | /** 8 | * Initialize the HeroRouter 9 | */ 10 | constructor() { 11 | this.router = Router(); 12 | this.init(); 13 | } 14 | 15 | /** 16 | * GET all Heroes. 17 | */ 18 | public getAll(req: Request, res: Response, next: NextFunction) { 19 | res.send(Heroes); 20 | } 21 | 22 | /** 23 | * GET one hero by id 24 | */ 25 | public getOne(req: Request, res: Response, next: NextFunction) { 26 | let query = parseInt(req.params.id); 27 | let hero = Heroes.find(hero => hero.id === query); 28 | if (hero) { 29 | res.status(200) 30 | .send({ 31 | message: 'Success', 32 | status: res.status, 33 | hero 34 | }); 35 | } 36 | else { 37 | res.status(404) 38 | .send({ 39 | message: 'No hero found with the given id.', 40 | status: res.status 41 | }); 42 | } 43 | } 44 | 45 | /** 46 | * Take each handler, and attach to one of the Express.Router's 47 | * endpoints. 48 | */ 49 | init() { 50 | this.router.get('/', this.getAll); 51 | this.router.get('/:id', this.getOne); 52 | } 53 | 54 | } 55 | 56 | // Create the HeroRouter, and export its configured Express.Router 57 | const heroRoutes = new HeroRouter(); 58 | heroRoutes.init(); 59 | 60 | export default heroRoutes.router; 61 | -------------------------------------------------------------------------------- /test/helloWorld.test.ts: -------------------------------------------------------------------------------- 1 | import * as mocha from 'mocha'; 2 | import * as chai from 'chai'; 3 | import chaiHttp = require('chai-http'); 4 | 5 | import app from '../src/App'; 6 | 7 | chai.use(chaiHttp); 8 | const expect = chai.expect; 9 | 10 | describe('baseRoute', () => { 11 | 12 | it('should be json', () => { 13 | return chai.request(app).get('/') 14 | .then(res => { 15 | expect(res.type).to.eql('application/json'); 16 | }); 17 | }); 18 | 19 | it('should have a message prop', () => { 20 | return chai.request(app).get('/') 21 | .then(res => { 22 | expect(res.body.message).to.eql('Hello World!'); 23 | }); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /test/hero.test.ts: -------------------------------------------------------------------------------- 1 | import * as mocha from 'mocha'; 2 | import * as chai from 'chai'; 3 | import chaiHttp = require('chai-http'); 4 | 5 | import app from '../src/App'; 6 | 7 | chai.use(chaiHttp); 8 | const expect = chai.expect; 9 | 10 | describe('GET api/v1/heroes', () => { 11 | 12 | it('responds with JSON array', () => { 13 | return chai.request(app).get('/api/v1/heroes') 14 | .then(res => { 15 | expect(res.status).to.equal(200); 16 | expect(res).to.be.json; 17 | expect(res.body).to.be.an('array'); 18 | expect(res.body).to.have.length(5); 19 | }); 20 | }); 21 | 22 | it('should include Wolverine', () => { 23 | return chai.request(app).get('/api/v1/heroes') 24 | .then(res => { 25 | let Wolverine = res.body.find(hero => hero.name === 'Wolverine'); 26 | expect(Wolverine).to.exist; 27 | expect(Wolverine).to.have.all.keys([ 28 | 'id', 29 | 'name', 30 | 'aliases', 31 | 'occupation', 32 | 'gender', 33 | 'height', 34 | 'hair', 35 | 'eyes', 36 | 'powers' 37 | ]); 38 | }); 39 | }); 40 | 41 | describe('GET api/v1/heroes/:id', () => { 42 | 43 | it('responds with single JSON object', () => { 44 | return chai.request(app).get('/api/v1/heroes/1') 45 | .then(res => { 46 | expect(res.status).to.equal(200); 47 | expect(res).to.be.json; 48 | expect(res.body).to.be.an('object'); 49 | }); 50 | }); 51 | 52 | it('should return Luke Cage', () => { 53 | return chai.request(app).get('/api/v1/heroes/1') 54 | .then(res => { 55 | expect(res.body.hero.name).to.equal('Luke Cage'); 56 | }); 57 | }); 58 | 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "dist" 6 | }, 7 | "include": [ 8 | "src/**/*.ts" 9 | ], 10 | "exclude": [ 11 | "node_modules" 12 | ] 13 | } 14 | --------------------------------------------------------------------------------