├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── __tests__ ├── controllers │ └── index-controller.test.ts └── vscode-restclient │ ├── article-requests.http │ ├── index-requests.http │ ├── token-requests.http │ └── user-requests.http ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── app.ts ├── controllers │ ├── articles-controller.ts │ ├── index-controller.ts │ ├── token-controller.ts │ └── users-controller.ts ├── environment.ts ├── models │ ├── base-rest-model.ts │ ├── entity │ │ ├── user-group.ts │ │ ├── user-session.ts │ │ └── user.ts │ └── schema │ │ ├── user-group.ts │ │ ├── user-session.ts │ │ └── user.ts ├── routes │ ├── articles-router.ts │ ├── main-router.ts │ ├── token-router.ts │ └── users-router.ts └── server.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | # Custom 42 | /dev 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Philipp John 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-api-typescript-template 2 | 3 | This is a test or template project for playing around with NodeJS + ExpressJS and Jest as test framework with TypeScript support. 4 | 5 | ## Development server 6 | 7 | Run `npm run dev` for a dev server. Navigate to `http://localhost:3000/`. 8 | 9 | ## Production server 10 | 11 | Run `npm run prod` for a prod server. Navigate to `http://localhost:3000/`. 12 | 13 | ## Build 14 | 15 | Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. 16 | 17 | ## Lint 18 | 19 | Run `npm run lint` to start a linter run. 20 | 21 | ## Running unit tests 22 | 23 | Run `npm run test` to execute the unit tests. 24 | 25 | ## Manual Rest-Client tests 26 | 27 | 1. Install this VSCode-Addon: https://github.com/Huachao/vscode-restclient 28 | 2. Open `tests/vscode-restclient-test.http` within Visual Studio Code and run the steps or define some additional 29 | -------------------------------------------------------------------------------- /__tests__/controllers/index-controller.test.ts: -------------------------------------------------------------------------------- 1 | import * as request from 'supertest'; 2 | import app from '../../src/app'; 3 | 4 | describe('index route', () => { 5 | 6 | // beforeEach(() => { 7 | // console.log('beforeEach triggered'); 8 | // }); 9 | 10 | // afterEach(() => { 11 | // console.log('afterEach triggered'); 12 | // }); 13 | 14 | // beforeAll(() => { 15 | // console.log('beforeAll triggered'); 16 | // }); 17 | 18 | // afterAll(() => { 19 | // console.log('afterAll triggered'); 20 | // }); 21 | 22 | test('app to be defined', () => { 23 | expect(app).toBeDefined(); 24 | }); 25 | 26 | test('test response statusCode', () => { 27 | expect.assertions(1); 28 | return request(app).get('/').then(response => { 29 | expect((response).statusCode).toBe(200); 30 | }); 31 | }); 32 | 33 | test('test response body', async () => { 34 | expect.assertions(1); 35 | const response = await request(app).get('/'); 36 | expect((response).body).toEqual({meta: {code: 0, message: 'The api is online!'}}); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /__tests__/vscode-restclient/article-requests.http: -------------------------------------------------------------------------------- 1 | # https://github.com/Huachao/vscode-restclient 2 | 3 | POST http://localhost:3000/article 4 | content-type: application/json 5 | 6 | { 7 | "name": "sample", 8 | "time": "Wed, 21 Oct 2015 18:27:50 GMT" 9 | } 10 | 11 | ### 12 | 13 | GET http://localhost:3000/article 14 | content-type: application/json 15 | 16 | ### 17 | 18 | GET http://localhost:3000/article/123 19 | content-type: application/json 20 | 21 | ### 22 | 23 | DELETE http://localhost:3000/article/123 24 | content-type: application/json 25 | -------------------------------------------------------------------------------- /__tests__/vscode-restclient/index-requests.http: -------------------------------------------------------------------------------- 1 | # https://github.com/Huachao/vscode-restclient 2 | 3 | GET http://localhost:3000 4 | 5 | ### 6 | -------------------------------------------------------------------------------- /__tests__/vscode-restclient/token-requests.http: -------------------------------------------------------------------------------- 1 | # https://github.com/Huachao/vscode-restclient 2 | 3 | POST http://localhost:3000/token 4 | 5 | ### 6 | 7 | // Invalid Token if send without auth header 8 | GET http://localhost:3000/token 9 | 10 | ### 11 | 12 | // Should respond with the token data 13 | // or "jwt expired" 14 | GET http://localhost:3000/token 15 | Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjp7ImlkIjoxLCJ1c2VybmFtZSI6InRlc3QiLCJtYWlsIjoidGVzdC50ZXN0QHRlc3QuY29tIn0sImlhdCI6MTUzNTE1NDA0MywiZXhwIjoxNTM1MTU0MDczLCJpc3MiOiJleHByZXNzLWFwaS10eXBlc2NyaXB0LXRlbXBsYXRlIn0.fSgHfu8w4vMLSg-ZdNW9tggCJ-cSOemZLmv4qF4e85U 16 | 17 | ### 18 | 19 | DELETE http://localhost:3000/token 20 | 21 | ### 22 | -------------------------------------------------------------------------------- /__tests__/vscode-restclient/user-requests.http: -------------------------------------------------------------------------------- 1 | # https://github.com/Huachao/vscode-restclient 2 | 3 | POST http://localhost:3000/user 4 | 5 | ### 6 | 7 | GET http://localhost:3000/user 8 | 9 | ### 10 | 11 | GET http://localhost:3000/user/1 12 | 13 | ### 14 | 15 | PUT http://localhost:3000/user/1 16 | 17 | ### 18 | 19 | DELETE http://localhost:3000/user/1 20 | 21 | ### 22 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "ignore": ["src/**/*.spec.ts"], 5 | "exec": "ts-node ./src/server.ts" 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-api-typescript-template", 3 | "version": "0.0.2", 4 | "description": "This is a template and test project for Node.js + Express.js + TypeOrm as a REST-Api tested with Jest", 5 | "main": "src/server.ts", 6 | "scripts": { 7 | "start": "npm run dev", 8 | "dev": "cross-env NODE_ENV=development nodemon ./src/server.ts", 9 | "prod": "cross-env NODE_ENV=production node ./dist/server.js", 10 | "test": "jest", 11 | "build": "tsc", 12 | "lint": "tslint \"src/**/*.ts\" -c \"./tslint.json\"" 13 | }, 14 | "author": "Philipp John (JPlace)", 15 | "license": "MIT", 16 | "bugs": "https://github.com/JohnnyDevNull/express-api-typescript-template/issues", 17 | "dependencies": { 18 | "bcrypt": "^5.0.0", 19 | "body-parser": "^1.19.0", 20 | "cookie-parser": "^1.4.4", 21 | "errorhandler": "^1.5.1", 22 | "express": "^4.17.1", 23 | "express-jwt": "^6.0.0", 24 | "jsonwebtoken": "^8.5.1", 25 | "method-override": "^3.0.0", 26 | "morgan": "^1.9.1", 27 | "mysql2": "^2.0.1", 28 | "nodemailer": "^6.4.16", 29 | "reflect-metadata": "^0.1.13", 30 | "sanitize": "^2.1.0", 31 | "typeorm": "^0.2.25" 32 | }, 33 | "devDependencies": { 34 | "@types/bcrypt": "^3.0.0", 35 | "@types/body-parser": "^1.17.1", 36 | "@types/cookie-parser": "^1.4.2", 37 | "@types/errorhandler": "0.0.32", 38 | "@types/express": "^4.17.2", 39 | "@types/jest": "^24.0.23", 40 | "@types/jsonwebtoken": "^8.3.5", 41 | "@types/method-override": "0.0.31", 42 | "@types/morgan": "^1.7.37", 43 | "@types/mysql": "^2.15.8", 44 | "@types/node": "^12.12.14", 45 | "@types/nodemailer": "^6.2.2", 46 | "@types/supertest": "^2.0.8", 47 | "cross-env": "^6.0.3", 48 | "cross-os": "^1.3.0", 49 | "jest": "^24.9.0", 50 | "nodemon": "^2.0.1", 51 | "supertest": "^4.0.2", 52 | "ts-jest": "^24.2.0", 53 | "ts-node": "^8.5.4", 54 | "tslint": "^5.20.1", 55 | "typescript": "^3.7.2" 56 | }, 57 | "jest": { 58 | "transform": { 59 | "^.+\\.tsx?$": "ts-jest" 60 | }, 61 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$", 62 | "moduleFileExtensions": [ 63 | "ts", 64 | "tsx", 65 | "js", 66 | "jsx", 67 | "json", 68 | "node" 69 | ] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import * as bodyParser from 'body-parser'; 2 | import * as errorhandler from 'errorhandler'; 3 | import * as express from 'express'; 4 | import * as morgan from 'morgan'; 5 | import { isProdMode } from './environment'; 6 | import { MainRouter } from './routes/main-router'; 7 | 8 | class App { 9 | 10 | public app: express.Application; 11 | public router: MainRouter = new MainRouter(); 12 | 13 | constructor() { 14 | this.app = express(); 15 | this.config(); 16 | this.router.attach(this.app); 17 | } 18 | 19 | private config(): void { 20 | 21 | if (!isProdMode()) { 22 | // only use in development 23 | // see: https://www.npmjs.com/package/errorhandler 24 | this.app.use(errorhandler()); 25 | } 26 | 27 | // support application/json type post data 28 | this.app.use(bodyParser.json()); 29 | // support application/x-www-form-urlencoded post data 30 | this.app.use(bodyParser.urlencoded({ extended: false })); 31 | 32 | // log all request in the Apache combined format 33 | // see: https://www.npmjs.com/package/morgan 34 | this.app.use(morgan('combined')); 35 | 36 | // sanitizing user input 37 | // see: https://www.npmjs.com/package/sanitize 38 | this.app.use(require('sanitize').middleware); 39 | } 40 | 41 | } 42 | 43 | export default new App().app; 44 | -------------------------------------------------------------------------------- /src/controllers/articles-controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { BaseRestModel } from '../models/base-rest-model'; 3 | 4 | export class ArticlesController { 5 | 6 | public addArticle (req: Request, res: Response) { 7 | const result: BaseRestModel = { 8 | meta: { 9 | code: 0, 10 | message: 'addArticle success!', 11 | }, 12 | }; 13 | 14 | res.json(result); 15 | } 16 | 17 | public getArticles (req: Request, res: Response) { 18 | const result: BaseRestModel = { 19 | meta: { 20 | code: 0, 21 | message: 'getArticles success!', 22 | hasMore: true, 23 | count: 2 24 | }, 25 | data: [ 26 | { id: 1, title: 'Lore Ipsum' }, 27 | { id: 2, title: 'Lore Ipsum 2' } 28 | ], 29 | }; 30 | 31 | res.json(result); 32 | } 33 | 34 | public getArticleById (req: Request, res: Response) { 35 | const articleId = +req.params.articleId; 36 | const result: BaseRestModel = { 37 | meta: { 38 | code: 0, 39 | message: 'getArticleById: ' + articleId, 40 | }, 41 | }; 42 | res.json(result); 43 | } 44 | 45 | public createArticle (req: Request, res: Response) { 46 | const result: BaseRestModel = { 47 | meta: { 48 | code: 0, 49 | message: 'createArticle success!', 50 | }, 51 | }; 52 | res.json(result); 53 | } 54 | 55 | public updateArticleById (req: Request, res: Response) { 56 | const articleId = +req.params.articleId; 57 | const result: BaseRestModel = { 58 | meta: { 59 | code: 0, 60 | message: 'updateArticleById: ' + articleId, 61 | }, 62 | }; 63 | res.json(result); 64 | } 65 | 66 | public deleteArticleById (req: Request, res: Response) { 67 | const articleId = +req.params.articleId; 68 | const result: BaseRestModel = { 69 | meta: { 70 | code: 0, 71 | message: 'deleteArticleById: ' + articleId, 72 | }, 73 | }; 74 | res.json(result); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/controllers/index-controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { BaseRestModel } from '../models/base-rest-model'; 3 | 4 | export class IndexController { 5 | public getIndex (req: Request, res: Response) { 6 | const result: BaseRestModel = { 7 | meta: { 8 | code: 0, 9 | message: 'The api is online!', 10 | } 11 | }; 12 | res.json(result); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/controllers/token-controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express'; 2 | import { BaseRestModel } from '../models/base-rest-model'; 3 | import * as jwt from 'jsonwebtoken'; 4 | import { environment } from '../environment'; 5 | 6 | export class TokenController { 7 | 8 | public getTokenInfo = (req: Request, res: Response) => { 9 | const result: BaseRestModel = { 10 | meta: { 11 | code: 0, 12 | message: 'getTokenInfo success!' 13 | }, 14 | data: { 15 | user: (req).user 16 | } 17 | }; 18 | res.json(result); 19 | } 20 | 21 | public createToken = (req: Request, res: Response) => { 22 | // Mock User 23 | const user = { 24 | id: 1, 25 | username: 'test', 26 | mail: 'test.test@test.com' 27 | }; 28 | 29 | const token = jwt.sign( 30 | { user }, 31 | environment.jwtSecret, 32 | { issuer: environment.jwtIssuer, expiresIn: '30s' } 33 | ); 34 | 35 | const result: BaseRestModel = { 36 | meta: { 37 | code: 0, 38 | message: 'success' 39 | }, 40 | data: { 41 | token: token 42 | } 43 | }; 44 | res.json(result); 45 | } 46 | 47 | public updateToken = (req: Request, res: Response) => { 48 | const result: BaseRestModel = { 49 | meta: { 50 | code: 0, 51 | message: 'updateToken success!' 52 | }, 53 | data: {} 54 | }; 55 | res.json(result); 56 | } 57 | 58 | public deleteToken = (req: Request, res: Response) => { 59 | const result: BaseRestModel = { 60 | meta: { 61 | code: 0, 62 | message: 'deleteToken success!' 63 | } 64 | }; 65 | res.json(result); 66 | } 67 | 68 | public verifyToken = (req: Request, res: Response, next: NextFunction) => { 69 | const bearerHeader = req.headers['authorization']; 70 | 71 | if (typeof bearerHeader !== 'undefined') { 72 | // split at the space 73 | const bearer = bearerHeader.split(' '); 74 | // get the token from the array 75 | const bearerToken = bearer[1]; 76 | // verify set the decoded user request data 77 | (req).user = jwt.verify(bearerToken, environment.jwtSecret); 78 | // call the next middleware 79 | next(); 80 | } else { 81 | const err = new Error('Invalid Token'); 82 | (err).status = '401'; 83 | throw err; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/controllers/users-controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from 'express'; 2 | import { BaseRestModel } from '../models/base-rest-model'; 3 | 4 | export class UsersController { 5 | 6 | public getUsers = (req: Request, res: Response) => { 7 | const result: BaseRestModel = { 8 | meta: { 9 | code: 0, 10 | message: 'getUsers success!' 11 | } 12 | }; 13 | res.json(result); 14 | } 15 | 16 | public createUser = (req: Request, res: Response) => { 17 | const result: BaseRestModel = { 18 | meta: { 19 | code: 0, 20 | message: 'createUser success!' 21 | } 22 | }; 23 | res.json(result); 24 | } 25 | 26 | public getUserById = (req: Request, res: Response) => { 27 | const result: BaseRestModel = { 28 | meta: { 29 | code: 0, 30 | message: 'getUserById success!' 31 | } 32 | }; 33 | res.json(result); 34 | } 35 | 36 | public updateUserById = (req: Request, res: Response) => { 37 | const result: BaseRestModel = { 38 | meta: { 39 | code: 0, 40 | message: 'updateUserById success!' 41 | } 42 | }; 43 | res.json(result); 44 | } 45 | 46 | public deleteUserById = (req: Request, res: Response) => { 47 | const result: BaseRestModel = { 48 | meta: { 49 | code: 0, 50 | message: 'deleteUserById success!' 51 | } 52 | }; 53 | res.json(result); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/environment.ts: -------------------------------------------------------------------------------- 1 | import { ConnectionOptions } from 'typeorm'; 2 | import { UserEntity } from './models/schema/user'; 3 | import { UserGroupEntity } from './models/schema/user-group'; 4 | import { UserSessionEntity } from './models/schema/user-session'; 5 | 6 | export const environment = { 7 | production: false, 8 | jwtSecret: 'my!Super$Secret+2018=', 9 | jwtIssuer: 'express-api-typescript-template' 10 | }; 11 | 12 | export function isProdMode(): boolean { 13 | return environment.production; 14 | } 15 | 16 | export function getDBConf(): ConnectionOptions { 17 | return { 18 | type: 'mysql', 19 | host: 'localhost', 20 | port: 3306, 21 | username: 'root', 22 | password: '', 23 | database: 'orm_express_test', 24 | synchronize: true, 25 | entities: [ 26 | UserEntity, 27 | UserGroupEntity, 28 | UserSessionEntity 29 | ] 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /src/models/base-rest-model.ts: -------------------------------------------------------------------------------- 1 | export interface BaseRestModel { 2 | meta: { 3 | code: any; 4 | message?: any; 5 | count?: number; 6 | hasMore?: boolean; 7 | limit?: number; 8 | offset?: number; 9 | }; 10 | data?: any; 11 | } 12 | -------------------------------------------------------------------------------- /src/models/entity/user-group.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | export interface UserGroup { 4 | id: Number; 5 | name: String; 6 | users?: User[]; 7 | } 8 | -------------------------------------------------------------------------------- /src/models/entity/user-session.ts: -------------------------------------------------------------------------------- 1 | import { User } from './user'; 2 | 3 | export interface UserSession { 4 | id: number; 5 | token: string; 6 | data: string; 7 | user?: User; 8 | } 9 | -------------------------------------------------------------------------------- /src/models/entity/user.ts: -------------------------------------------------------------------------------- 1 | import { UserGroup } from './user-group'; 2 | import { UserSession } from './user-session'; 3 | 4 | export interface User { 5 | id?: number; 6 | firstName: string; 7 | lastName: string; 8 | userName: string; 9 | password?: string; 10 | email?: string; 11 | createdAt?: Date; 12 | updateAt?: Date; 13 | activated?: boolean; 14 | activatedAt?: Date; 15 | locked?: boolean; 16 | lockedAt?: Date; 17 | session?: UserSession; 18 | groups?: UserGroup[]; 19 | } 20 | -------------------------------------------------------------------------------- /src/models/schema/user-group.ts: -------------------------------------------------------------------------------- 1 | import { EntitySchema } from 'typeorm'; 2 | import { UserGroup } from '../entity/user-group'; 3 | 4 | export const UserGroupEntity = new EntitySchema({ 5 | name: 'UserGroup', 6 | tableName: 'core_user_group', 7 | columns: { 8 | id: { 9 | type: Number, 10 | primary: true, 11 | generated: true, 12 | unsigned: true 13 | }, 14 | name: { 15 | type: String, 16 | length: 100 17 | } 18 | }, 19 | relations: { 20 | users: { 21 | type: 'many-to-many', 22 | target: 'User', 23 | inverseSide: 'groups' 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /src/models/schema/user-session.ts: -------------------------------------------------------------------------------- 1 | import { EntitySchema } from 'typeorm'; 2 | import { UserSession } from '../entity/user-session'; 3 | 4 | export const UserSessionEntity = new EntitySchema({ 5 | name: 'UserSession', 6 | tableName: 'core_user_session', 7 | columns: { 8 | id: { 9 | type: Number, 10 | primary: true, 11 | generated: true, 12 | unsigned: true 13 | }, 14 | token: { 15 | type: String 16 | }, 17 | data: { 18 | type: String, 19 | }, 20 | }, 21 | relations: { 22 | user: { 23 | target: 'User', 24 | type: 'one-to-one', 25 | inverseSide: 'session', 26 | joinColumn: true 27 | } 28 | } 29 | }); 30 | -------------------------------------------------------------------------------- /src/models/schema/user.ts: -------------------------------------------------------------------------------- 1 | import { EntitySchema } from 'typeorm'; 2 | import { User } from '../entity/user'; 3 | 4 | export const UserEntity = new EntitySchema({ 5 | name: 'User', 6 | tableName: 'core_user', 7 | columns: { 8 | id: { 9 | type: Number, 10 | primary: true, 11 | generated: true, 12 | unsigned: true 13 | }, 14 | firstName: { 15 | type: String, 16 | length: 200 17 | }, 18 | lastName: { 19 | type: String, 20 | length: 200 21 | }, 22 | userName: { 23 | type: String, 24 | length: 200 25 | }, 26 | password: { 27 | type: String, 28 | length: 64 29 | }, 30 | email: { 31 | type: String, 32 | length: 200 33 | }, 34 | createdAt: { 35 | type: Date, 36 | createDate: true 37 | }, 38 | updateAt: { 39 | type: Date, 40 | updateDate: true 41 | }, 42 | activated: { 43 | type: Boolean, 44 | width: 1, 45 | unsigned: true 46 | }, 47 | activatedAt: { 48 | type: Date 49 | }, 50 | locked: { 51 | type: Boolean, 52 | width: 1, 53 | unsigned: true 54 | }, 55 | lockedAt: { 56 | type: Date 57 | } 58 | }, 59 | relations: { 60 | session: { 61 | target: 'UserSession', 62 | type: 'one-to-one', 63 | cascade: true, 64 | inverseSide: 'user' 65 | }, 66 | groups: { 67 | target: 'UserGroup', 68 | type: 'many-to-many', 69 | joinTable: { name: 'core_user_group_map' }, 70 | cascade: true, 71 | inverseSide: 'users' 72 | } 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /src/routes/articles-router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'express'; 2 | import { ArticlesController } from '../controllers/articles-controller'; 3 | 4 | export class ArtcilesRouter { 5 | 6 | public articleCtrl: ArticlesController = new ArticlesController(); 7 | 8 | public attach(app: Application): void { 9 | // Articles 10 | app.route('/article') 11 | // GET - List 12 | .get(this.articleCtrl.getArticles) 13 | // POST - Create 14 | .post(this.articleCtrl.createArticle); 15 | 16 | // Article Single 17 | app.route('/article/:articleId') 18 | // GET Single 19 | .get(this.articleCtrl.getArticleById) 20 | // Update Article 21 | .put(this.articleCtrl.updateArticleById) 22 | // Delete Article 23 | .delete(this.articleCtrl.deleteArticleById); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/main-router.ts: -------------------------------------------------------------------------------- 1 | import { IndexController } from '../controllers/index-controller'; 2 | import { isProdMode } from '../environment'; 3 | import { ArtcilesRouter } from './articles-router'; 4 | import { TokenRouter } from './token-router'; 5 | import { UsersRouter } from './users-router'; 6 | 7 | export class MainRouter { 8 | 9 | public indexCtrl: IndexController = new IndexController(); 10 | 11 | public attach(app): void { 12 | this.addRoutes(app); 13 | this.addErrorHandler(app); 14 | } 15 | 16 | private addRoutes(app) { 17 | app.route('/').get(this.indexCtrl.getIndex); 18 | 19 | const articlesRouter = new ArtcilesRouter(); 20 | articlesRouter.attach(app); 21 | 22 | const usersRouter = new UsersRouter(); 23 | usersRouter.attach(app); 24 | 25 | const tokenRouter = new TokenRouter(); 26 | tokenRouter.attach(app); 27 | } 28 | 29 | private addErrorHandler(app) { 30 | // catch 404 and forward to error handler 31 | app.use(function(req, res, next) { 32 | const err = new Error('Not Found'); 33 | (err).status = 404; 34 | next(err); 35 | }); 36 | // development error handler 37 | if (!isProdMode()) { 38 | app.use(function(err, req, res, next) { 39 | console.log(err.stack); 40 | res.status(err.status || 500); 41 | res.json({'meta': { 42 | code: err.status, 43 | message: err.message 44 | }}); 45 | }); 46 | } 47 | // production error handler 48 | app.use(function(err, req, res, next) { 49 | res.status(err.status || 500); 50 | res.json({'meta': { 51 | code: err.status, 52 | message: err.message 53 | }}); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/routes/token-router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'express'; 2 | import { TokenController } from '../controllers/token-controller'; 3 | 4 | export class TokenRouter { 5 | 6 | public tokenCtrl: TokenController = new TokenController(); 7 | 8 | public attach(app: Application): void { 9 | app.route('/token') 10 | // GET Token validity 11 | .get(this.tokenCtrl.verifyToken, this.tokenCtrl.getTokenInfo) 12 | // POST Get new Token 13 | .post(this.tokenCtrl.createToken) 14 | // DELETE Token 15 | .delete(this.tokenCtrl.deleteToken); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/users-router.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'express'; 2 | import { UsersController } from '../controllers/users-controller'; 3 | 4 | export class UsersRouter { 5 | 6 | public usersCtrl: UsersController = new UsersController(); 7 | 8 | public attach(app: Application): void { 9 | 10 | app.route('/user') 11 | .get(this.usersCtrl.getUsers) 12 | .post(this.usersCtrl.createUser); 13 | 14 | app.route('/user/:userId') 15 | .get(this.usersCtrl.getUserById) 16 | .put(this.usersCtrl.updateUserById) 17 | .delete(this.usersCtrl.deleteUserById); 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/server.ts: -------------------------------------------------------------------------------- 1 | import { createConnection } from 'typeorm'; 2 | import app from './app'; 3 | import { environment, getDBConf } from './environment'; 4 | 5 | const PORT = process.env.PORT || 3000; 6 | 7 | if (process.env.NODE_ENV === 'production') { 8 | environment.production = true; 9 | } else { 10 | console.log('The server run in development mode!'); 11 | } 12 | 13 | createConnection(getDBConf()).then(async connection => { 14 | app.listen(PORT, () => console.log('Express server listening on port ' + PORT)); 15 | }).catch(error => console.log('TypeORM connection error: ', error)); 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "moduleResolution": "node", 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "target": "es5", 12 | "typeRoots": [ 13 | "node_modules/@types" 14 | ], 15 | "lib": [ 16 | "es2017", 17 | "dom" 18 | ], 19 | }, 20 | "include": [ 21 | "src/**/*.ts" 22 | ], 23 | "exclude": [ 24 | "node_modules" 25 | ], 26 | } 27 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "arrow-return-shorthand": true, 4 | "callable-types": true, 5 | "class-name": true, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "curly": true, 11 | "deprecation": true, 12 | "eofline": true, 13 | "forin": true, 14 | "import-blacklist": [ 15 | true, 16 | "rxjs/Rx" 17 | ], 18 | "import-spacing": true, 19 | "indent": [ 20 | true, 21 | "spaces" 22 | ], 23 | "interface-over-type-literal": true, 24 | "label-position": true, 25 | "max-line-length": [ 26 | true, 27 | 140 28 | ], 29 | "member-access": false, 30 | "member-ordering": [ 31 | true, 32 | { 33 | "order": [ 34 | "static-field", 35 | "instance-field", 36 | "static-method", 37 | "instance-method" 38 | ] 39 | } 40 | ], 41 | "no-arg": true, 42 | "no-bitwise": true, 43 | "no-console": [ 44 | true, 45 | "debug", 46 | "info", 47 | "time", 48 | "timeEnd", 49 | "trace" 50 | ], 51 | "no-construct": true, 52 | "no-debugger": true, 53 | "no-duplicate-super": true, 54 | "no-empty": false, 55 | "no-empty-interface": true, 56 | "no-eval": true, 57 | "no-inferrable-types": [ 58 | true, 59 | "ignore-params" 60 | ], 61 | "no-misused-new": true, 62 | "no-non-null-assertion": true, 63 | "no-shadowed-variable": true, 64 | "no-string-literal": false, 65 | "no-string-throw": true, 66 | "no-switch-case-fall-through": true, 67 | "no-trailing-whitespace": true, 68 | "no-unnecessary-initializer": true, 69 | "no-unused-expression": true, 70 | "no-unused-variable": true, 71 | "no-use-before-declare": true, 72 | "no-var-keyword": true, 73 | "object-literal-sort-keys": false, 74 | "one-line": [ 75 | true, 76 | "check-open-brace", 77 | "check-catch", 78 | "check-else", 79 | "check-whitespace" 80 | ], 81 | "prefer-const": true, 82 | "quotemark": [ 83 | true, 84 | "single" 85 | ], 86 | "radix": true, 87 | "semicolon": [ 88 | true, 89 | "always" 90 | ], 91 | "triple-equals": [ 92 | true, 93 | "allow-null-check" 94 | ], 95 | "typedef-whitespace": [ 96 | true, 97 | { 98 | "call-signature": "nospace", 99 | "index-signature": "nospace", 100 | "parameter": "nospace", 101 | "property-declaration": "nospace", 102 | "variable-declaration": "nospace" 103 | } 104 | ], 105 | "unified-signatures": true, 106 | "variable-name": false, 107 | "whitespace": [ 108 | true, 109 | "check-branch", 110 | "check-decl", 111 | "check-operator", 112 | "check-separator", 113 | "check-type" 114 | ] 115 | } 116 | } 117 | --------------------------------------------------------------------------------