├── .DS_Store ├── .env ├── .gitignore ├── Procfile ├── README.md ├── ormconfig.json ├── package-lock.json ├── package.json ├── postmanCollection └── Node-typescript-boilerplate-postgres.postman_collection.json ├── src ├── controllers │ ├── RoleController.ts │ └── UserController.ts ├── entity │ ├── Role.ts │ └── User.ts ├── index.ts ├── middlewares │ ├── Auth.ts │ ├── AuthRequestContext.ts │ └── IsAdmin.ts ├── response │ ├── InternalServerErrorResponse.ts │ ├── LoginResponse.ts │ └── RequestFailedResponse.ts ├── routes │ ├── RoleRouters.ts │ └── UserRoutes.ts └── types │ └── RoleType.ts └── tsconfig.json /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chiragmehta900/node-typescript-boilerplate-postgres/d4f3583c02eb1a8dea426a6ad8e1c26ea9c699de/.DS_Store -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | TOKEN_SECRET=9onelinesomniunnostri 2 | REFRESH_TOKEN_SECRET=qwertyuiopzxcvbnm 3 | PORT=5000 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node dist/index.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Node TypeScript Boilerplate Postgres 3 | 4 | Developer Ready: A comprehensive template. Works out of the box for most Node.js projects. 5 | This project is intended to be used with the latest Active LTS release of Node.js. 6 | ## Tech Stack 7 | 8 | **Server:** Node, Express 9 | 10 | **DataBase:** postgres, typeorm 11 | 12 | 13 | ## Run Locally 14 | 15 | Clone the project 16 | 17 | ```bash 18 | git clone https://github.com/chiragmehta900/node-typescript-boilerplate-postgres 19 | ``` 20 | 21 | Go to the project directory 22 | 23 | ```bash 24 | cd node-typescript-boilerplate-postgres 25 | ``` 26 | 27 | Install dependencies 28 | 29 | ```bash 30 | npm install 31 | ``` 32 | 33 | Start the tsc 34 | 35 | ```bash 36 | npm run watch 37 | ``` 38 | 39 | Start the server 40 | 41 | ```bash 42 | npm run dev 43 | ``` 44 | 45 | 46 | ## Environment Variables 47 | 48 | To run this project, you will need to add the following environment variables to your .env file 49 | 50 | `PORT` 51 | 52 | `TOKEN_SECRET` 53 | 54 | `REFRESH_TOKEN_SECRET` 55 | 56 | 57 | 58 | ## Documentation 59 | 60 | [Documentation](https://linktodocumentation) 61 | 62 | 63 | ## API Reference 64 | 65 | #### Create user 66 | 67 | ```http 68 | POST /user/create 69 | ``` 70 | 71 | | Parameter | Type | Description | 72 | | :-------- | :------- | :-------------------------------- | 73 | | `firstName` | `string` | **Required**. Your API key | 74 | | `lastName` | `string` | **Required**. Your API key | 75 | | `email` | `string` | **Required**. Your API key | 76 | | `password` | `string` | **Required**. Your API key | 77 | | `phoneNumber` | `number` | **Required**. Your API key | 78 | 79 | #### login user 80 | 81 | ```http 82 | POST /user/login 83 | ``` 84 | 85 | | Parameter | Type | Description | 86 | | :-------- | :------- | :-------------------------------- | 87 | | `phoneNumber` | `number` | **Required**. Your API key | 88 | | `password` | `string` | **Required**. Your API key | 89 | 90 | 91 | #### Get all users 92 | 93 | ```http 94 | GET /user/list 95 | ``` 96 | 97 | | Parameter | Type | Description | 98 | | :-------- | :------- | :------------------------- | 99 | | `id` | `uuid` | **Required**. Your API key | 100 | | `firstName` | `string` | **Required**. Your API key | 101 | | `lastName` | `string` | **Required**. Your API key | 102 | | `email` | `string` | **Required**. Your API key | 103 | | `password` | `string` | **Required**. Your API key | 104 | | `profileImage` | `string` | **Required**. Your API key | 105 | | `isActive` | `boolean` | **Required**. Your API key | 106 | | `phoneNumber` | `number` | **Required**. Your API key | 107 | | `tocken` | `string` | **Required**. Your API key | 108 | | `refreshToken` | `string` | **Required**. Your API key | 109 | 110 | A postman collection has been added for better understanding. 111 | 112 | 113 | ## Badges 114 | 115 | Add badges from somewhere like: [shields.io](https://shields.io/) 116 | 117 | [![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) 118 | 119 | [![GPLv3 License](https://img.shields.io/badge/License-GPL%20v3-yellow.svg)](https://opensource.org/licenses/) 120 | 121 | [![AGPL License](https://img.shields.io/badge/license-AGPL-blue.svg)](http://www.gnu.org/licenses/agpl-3.0) 122 | 123 | 124 | ## Author 125 | 126 | - [@chiragmehta900](https://www.github.com/chiragmehta900) 127 | 128 | 129 | ## Support 130 | 131 | For support, email chiragmehta900@gmial.com or join our Slack channel. -------------------------------------------------------------------------------- /ormconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "postgres", 3 | "host": "ec2-44-195-162-77.compute-1.amazonaws.com", 4 | "port": 5432, 5 | "username": "nibkhmvvubbsxa", 6 | "password": "be77dee1c0471b06dd41d7c9d81ebda2fefcea51d3158f3fb0f8592c56831327", 7 | "database": "d8d9it747v8icr", 8 | "synchronize": true, 9 | "logging": true, 10 | "ssl": { 11 | "rejectUnauthorized": false 12 | }, 13 | "entities": [ 14 | "dist/entity/**/*.js" 15 | ], 16 | "migrations": [ 17 | "dist/migration/**/*.js" 18 | ], 19 | "subscribers": [ 20 | "dist/subscriber/**/*.js" 21 | ], 22 | "cli": { 23 | "entitiesDir": "./src/entity", 24 | "migrationsDir": "./src/migration", 25 | "subscribersDir": "./src/subscriber" 26 | } 27 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-typescript-boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.ts", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "tsc", 9 | "build:lib": "tsc", 10 | "build:asset": "cp -R src/templates/ dist/templates", 11 | "build:mkdir": "mkdir dist/uploads && mkdir dist/uploads/images dist/uploads/pdf dist/uploads/video", 12 | "build:ormconfig": "copyfiles -f ormconfig.js dist/", 13 | "seed": "node --max-old-space-size=8192 dist/services/seeder.js", 14 | "watch": "tsc -w", 15 | "dev": "nodemon dist/index.js", 16 | "start": "node dist/index.js" 17 | }, 18 | "keywords": [], 19 | "author": "chiragmehta900", 20 | "license": "ISC", 21 | "devDependencies": { 22 | "@types/bcryptjs": "^2.4.2", 23 | "@types/cors": "^2.8.12", 24 | "@types/express": "^4.17.13", 25 | "@types/geojson": "^7946.0.8", 26 | "@types/jsonwebtoken": "^8.5.5", 27 | "@types/moment": "^2.13.0", 28 | "@types/pino": "^6.3.11", 29 | "nodemon": "^2.0.12", 30 | "ts-node": "^10.2.1", 31 | "typescript": "^4.3.5" 32 | }, 33 | "dependencies": { 34 | "bcryptjs": "^2.4.3", 35 | "body-parser": "^1.20.0", 36 | "class-transformer": "^0.4.0", 37 | "cors": "^2.8.5", 38 | "express": "^4.17.1", 39 | "fs": "^0.0.1-security", 40 | "geojson": "^0.5.0", 41 | "http": "^0.0.1-security", 42 | "jsonwebtoken": "^8.5.1", 43 | "mongoose": "^5.13.5", 44 | "pg": "^8.7.1", 45 | "typeorm": "^0.2.37", 46 | "typeorm-pagination": "^2.0.3", 47 | "uuid": "^8.3.2" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /postmanCollection/Node-typescript-boilerplate-postgres.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "ceac308b-4ebe-4eb0-b12c-3380808ea824", 4 | "name": "Node-typescript-boilerplate-postgres", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", 6 | "_exporter_id": "12358364" 7 | }, 8 | "item": [ 9 | { 10 | "name": "Role", 11 | "item": [ 12 | { 13 | "name": "get all role", 14 | "request": { 15 | "method": "GET", 16 | "header": [], 17 | "url": { 18 | "raw": "{{http}}role/getall", 19 | "host": [ 20 | "{{http}}role" 21 | ], 22 | "path": [ 23 | "getall" 24 | ] 25 | } 26 | }, 27 | "response": [] 28 | }, 29 | { 30 | "name": "create role", 31 | "request": { 32 | "method": "POST", 33 | "header": [], 34 | "body": { 35 | "mode": "raw", 36 | "raw": "{\r\n \"name\":\"user\"\r\n}", 37 | "options": { 38 | "raw": { 39 | "language": "json" 40 | } 41 | } 42 | }, 43 | "url": { 44 | "raw": "{{http}}role/create", 45 | "host": [ 46 | "{{http}}role" 47 | ], 48 | "path": [ 49 | "create" 50 | ] 51 | } 52 | }, 53 | "response": [] 54 | } 55 | ] 56 | }, 57 | { 58 | "name": "User", 59 | "item": [ 60 | { 61 | "name": "create user", 62 | "request": { 63 | "method": "POST", 64 | "header": [], 65 | "body": { 66 | "mode": "raw", 67 | "raw": "{\r\n \"firstName\": \"Chirag\",\r\n \"lastName\": \"Mehta\",\r\n \"email\": \"chiragmehta900@gmail.com\",\r\n \"password\": \"qazxsw123\",\r\n \"phoneNumber\": \"8460339810\"\r\n}", 68 | "options": { 69 | "raw": { 70 | "language": "json" 71 | } 72 | } 73 | }, 74 | "url": { 75 | "raw": "{{http}}user/create", 76 | "host": [ 77 | "{{http}}user" 78 | ], 79 | "path": [ 80 | "create" 81 | ] 82 | } 83 | }, 84 | "response": [] 85 | }, 86 | { 87 | "name": "user login", 88 | "request": { 89 | "method": "POST", 90 | "header": [], 91 | "body": { 92 | "mode": "raw", 93 | "raw": "{\r\n \"phonenumber\": \"8460339810\",\r\n \"password\": \"qazxsw123\"\r\n}", 94 | "options": { 95 | "raw": { 96 | "language": "json" 97 | } 98 | } 99 | }, 100 | "url": { 101 | "raw": "{{http}}user/login", 102 | "host": [ 103 | "{{http}}user" 104 | ], 105 | "path": [ 106 | "login" 107 | ] 108 | } 109 | }, 110 | "response": [] 111 | }, 112 | { 113 | "name": "user detail", 114 | "request": { 115 | "auth": { 116 | "type": "bearer", 117 | "bearer": [ 118 | { 119 | "key": "token", 120 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYzNlNGExLWNhOTktNDcxMC1iMDZiLTIxN2JiMmJlNmQxOSIsInBob25lbnVtYmVyIjoiODQ2MDMzOTgxMCIsInRva2VuVmVyc2lvbiI6MCwiaWF0IjoxNjU3MzU2NTQxLCJleHAiOjE2NTc5NjEzNDF9.WtKSGZNtQwx7UVlVDJ6hbYGlvAxI5qY_ogUi37pYMC4", 121 | "type": "string" 122 | } 123 | ] 124 | }, 125 | "method": "GET", 126 | "header": [], 127 | "url": { 128 | "raw": "{{http}}user", 129 | "host": [ 130 | "{{http}}user" 131 | ] 132 | } 133 | }, 134 | "response": [] 135 | }, 136 | { 137 | "name": "user list", 138 | "request": { 139 | "method": "GET", 140 | "header": [], 141 | "url": { 142 | "raw": "{{http}}user/list", 143 | "host": [ 144 | "{{http}}user" 145 | ], 146 | "path": [ 147 | "list" 148 | ] 149 | } 150 | }, 151 | "response": [] 152 | }, 153 | { 154 | "name": "update user", 155 | "request": { 156 | "auth": { 157 | "type": "bearer", 158 | "bearer": [ 159 | { 160 | "key": "token", 161 | "value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjVkYzNlNGExLWNhOTktNDcxMC1iMDZiLTIxN2JiMmJlNmQxOSIsInBob25lbnVtYmVyIjoiODQ2MDMzOTgxMCIsInRva2VuVmVyc2lvbiI6MCwiaWF0IjoxNjU3MzU2NTQxLCJleHAiOjE2NTc5NjEzNDF9.WtKSGZNtQwx7UVlVDJ6hbYGlvAxI5qY_ogUi37pYMC4", 162 | "type": "string" 163 | } 164 | ] 165 | }, 166 | "method": "PATCH", 167 | "header": [], 168 | "body": { 169 | "mode": "raw", 170 | "raw": "{\r\n \"firstName\": \"Chirag\",\r\n \"lastName\": \"Mehta\",\r\n \"email\": \"chiragmehta900@gmail.com\",\r\n \"password\": \"qazxsw123\",\r\n \"phoneNumber\": \"8460339810\"\r\n}", 171 | "options": { 172 | "raw": { 173 | "language": "json" 174 | } 175 | } 176 | }, 177 | "url": { 178 | "raw": "{{http}}user/update", 179 | "host": [ 180 | "{{http}}user" 181 | ], 182 | "path": [ 183 | "update" 184 | ] 185 | } 186 | }, 187 | "response": [] 188 | } 189 | ] 190 | } 191 | ] 192 | } -------------------------------------------------------------------------------- /src/controllers/RoleController.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { Role } from "../entity/Role"; 3 | import { InternalServerError } from "./../response/InternalServerErrorResponse"; 4 | import { RequestFailed } from "../response/RequestFailedResponse"; 5 | 6 | export const getAllRole = async (_: Request, res: Response) => { 7 | try { 8 | const roles = await Role.find(); 9 | 10 | res.status(200).json({ 11 | success: true, 12 | roles, 13 | }); 14 | } catch (error) { 15 | return InternalServerError(res, error); 16 | } 17 | }; 18 | 19 | export const createRole = async (req: Request, res: Response) => { 20 | try { 21 | const name: string = req.body.name; 22 | 23 | if (!name || !name.trim().length) { 24 | return RequestFailed(res, 400, "name"); 25 | } 26 | 27 | const roles = new Role(); 28 | roles.name = name; 29 | await roles.save(); 30 | res.status(200).json({ 31 | success: true, 32 | roles, 33 | }); 34 | } catch (error) { 35 | return InternalServerError(res, error); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/controllers/UserController.ts: -------------------------------------------------------------------------------- 1 | import { compare, hash } from "bcryptjs"; 2 | import { classToPlain } from "class-transformer"; 3 | import { Request, Response } from "express"; 4 | import { Role } from "../entity/Role"; 5 | import { User } from "../entity/User"; 6 | import { InternalServerError } from "../response/InternalServerErrorResponse"; 7 | import { RequestFailed } from "../response/RequestFailedResponse"; 8 | import { RoleType } from "./../types/RoleType"; 9 | import { getConnection } from "typeorm"; 10 | import { AuthRequest } from "../middlewares/AuthRequestContext"; 11 | import { LoginResponse } from "../response/LoginResponse"; 12 | import jwt from "jsonwebtoken"; 13 | 14 | export const login = async (req: Request, res: Response) => { 15 | try { 16 | const phonenumber: string = req.body.phonenumber; 17 | const password: string = req.body.password; 18 | 19 | if (!phonenumber || !phonenumber.trim().length) { 20 | return RequestFailed(res, 400, "phonenumber"); 21 | } 22 | 23 | if (!password || !password.trim().length) { 24 | return RequestFailed(res, 400, "password"); 25 | } 26 | 27 | const user = await getConnection() 28 | .getRepository(User) 29 | .createQueryBuilder("user") 30 | .where("user.phoneNumber = :phonenumber", { phonenumber }) 31 | .leftJoinAndSelect("user.role", "role") 32 | .getOne(); 33 | 34 | if (!user) { 35 | return RequestFailed(res, 401, "Your email / password might be wrong."); 36 | } else { 37 | const isValidPass = await compare(password, user.password); 38 | if (!isValidPass) { 39 | return RequestFailed(res, 401, "You have entered a wrong password."); 40 | } 41 | if (!user.isActive) { 42 | return RequestFailed(res, 401, "Your account is not active."); 43 | } 44 | const data = { 45 | id: user.id, 46 | phonenumber: user.phoneNumber, 47 | tokenVersion: user.tokenVersion, 48 | }; 49 | const token = await jwt.sign(data, process.env.TOKEN_SECRET!, { 50 | expiresIn: "7d", 51 | }); 52 | 53 | const refreshToken = await jwt.sign( 54 | data, 55 | process.env.REFRESH_TOKEN_SECRET!, 56 | { 57 | expiresIn: "30d", 58 | } 59 | ); 60 | if (token) { 61 | res.status(202).json(LoginResponse(token, refreshToken, user)); 62 | 63 | if (user.role.name !== RoleType.admin) { 64 | user.save(); 65 | } 66 | } 67 | } 68 | } catch (error) { 69 | return InternalServerError(res, error); 70 | } 71 | }; 72 | 73 | export const createUser = async (req: Request, res: Response) => { 74 | try { 75 | const firstname: string = req.body.firstName; 76 | const lastname: string = req.body.lastName; 77 | const password: string = req.body.password; 78 | const profileImage: string = req.body.profileImage || ""; 79 | const phoneNumber: string = req.body.phoneNumber; 80 | const timestamp = req.body.timestamp 81 | ? new Date(req.body.timestamp) 82 | : new Date(); 83 | 84 | if (!firstname || !firstname.trim().length) { 85 | return RequestFailed(res, 400, "firstname"); 86 | } 87 | if (!lastname || !lastname.trim().length) { 88 | return RequestFailed(res, 400, "lastname"); 89 | } 90 | if (!password || !password.trim().length) { 91 | return RequestFailed(res, 400, "password"); 92 | } 93 | if (!phoneNumber || !phoneNumber.trim().length) { 94 | return RequestFailed(res, 400, "phonenumber"); 95 | } 96 | 97 | const role = await Role.findOne({ 98 | where: { name: RoleType.user }, 99 | }); 100 | 101 | const hashPassword = await hash(password, 12); 102 | 103 | if (role) { 104 | const user = new User(); 105 | user.firstName = firstname; 106 | user.lastName = lastname; 107 | user.role = role; 108 | user.password = hashPassword; 109 | user.phoneNumber = phoneNumber; 110 | user.profileImage = profileImage; 111 | user.last_updated = new Date(); 112 | user.timestamp = timestamp; 113 | await user.save(); 114 | 115 | const userResponse = classToPlain(user); 116 | res.status(200).json({ 117 | success: true, 118 | user: userResponse, 119 | }); 120 | } 121 | } catch (error) { 122 | return InternalServerError(res, error); 123 | } 124 | }; 125 | 126 | export const getAllUsers = async (req: Request, res: Response) => { 127 | try { 128 | const query = req.query.search; 129 | const users = await getConnection() 130 | .getRepository(User) 131 | .createQueryBuilder("user") 132 | .leftJoinAndSelect("user.role", "role") 133 | .where("user.firstName like :name", { name: `${query ? query : "%"}%` }) 134 | .orderBy("user.timestamp", "DESC") 135 | .paginate(); 136 | 137 | const { data, ...rest } = users; 138 | const newUsers: any[] = []; 139 | data.forEach((user: User) => { 140 | newUsers.push(classToPlain(user)); 141 | }); 142 | res.status(200).json({ 143 | success: true, 144 | users: newUsers, 145 | ...rest, 146 | }); 147 | } catch (error) { 148 | return InternalServerError(res, error); 149 | } 150 | }; 151 | 152 | export const getUserById = async (req: AuthRequest, res: Response) => { 153 | try { 154 | const user = await User.findOne(req.userId, { 155 | relations: ["role"], 156 | }); 157 | 158 | if (!user) { 159 | return RequestFailed(res, 404, "user", req.userId); 160 | } 161 | 162 | const plainUser = classToPlain(user); 163 | res.status(200).json({ 164 | success: true, 165 | user: plainUser, 166 | }); 167 | } catch (error) { 168 | return InternalServerError(res, error); 169 | } 170 | }; 171 | 172 | export const updateUser = async (req: AuthRequest, res: Response) => { 173 | try { 174 | const firstName = req.body.firstName; 175 | const lastName = req.body.lastName; 176 | const phoneNumber = req.body.phoneNumber; 177 | const email = req.body.email; 178 | const profileImage = req.body.profileImage || null; 179 | 180 | const user = await User.findOne(req.userId, { relations: ["role"] }); 181 | if (!user) { 182 | return RequestFailed(res, 404, "user", req.userId); 183 | } 184 | if (firstName) { 185 | user.firstName = firstName; 186 | } 187 | if (lastName) { 188 | user.lastName = lastName; 189 | } 190 | if (phoneNumber) { 191 | user.phoneNumber = phoneNumber; 192 | } 193 | if (email) { 194 | user.email = email; 195 | } 196 | if (profileImage) { 197 | user.profileImage = profileImage; 198 | } 199 | 200 | await user.save(); 201 | 202 | const userResponse = classToPlain(user); 203 | res.status(200).json({ 204 | success: true, 205 | user: userResponse, 206 | }); 207 | } catch (error) { 208 | return InternalServerError(res, error); 209 | } 210 | }; 211 | -------------------------------------------------------------------------------- /src/entity/Role.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseEntity, 3 | Column, 4 | Entity, 5 | PrimaryGeneratedColumn, 6 | } from "typeorm"; 7 | 8 | @Entity() 9 | 10 | export class Role extends BaseEntity { 11 | @PrimaryGeneratedColumn() 12 | id: number; 13 | 14 | @Column({ unique: true }) 15 | name: string; 16 | } 17 | -------------------------------------------------------------------------------- /src/entity/User.ts: -------------------------------------------------------------------------------- 1 | import { Exclude } from "class-transformer"; 2 | import { 3 | BaseEntity, 4 | Column, 5 | Entity, 6 | ManyToOne, 7 | PrimaryGeneratedColumn, 8 | } from "typeorm"; 9 | import { Role } from "./Role"; 10 | 11 | 12 | @Entity() 13 | export class User extends BaseEntity { 14 | @PrimaryGeneratedColumn("uuid") 15 | id: number; 16 | 17 | @ManyToOne(() => Role, (role) => role.id) 18 | role: Role; 19 | 20 | @Column({ unique: true }) 21 | firstName: string; 22 | 23 | @Column() 24 | lastName: string; 25 | 26 | @Column({ nullable: true, unique: true }) 27 | email: string; 28 | 29 | @Column() 30 | @Exclude() 31 | password: string; 32 | 33 | @Column({ nullable: true }) 34 | profileImage: string; 35 | 36 | @Column({ default: true }) 37 | isActive: boolean; 38 | 39 | @Column({ nullable: true }) 40 | fcmToken: string; 41 | 42 | @Column({ default: "0", unique: true }) 43 | phoneNumber: string; 44 | 45 | @Column({ default: 0 }) 46 | tokenVersion: number; 47 | 48 | @Column({ type: "timestamp", nullable: true }) 49 | last_updated: Date; 50 | 51 | @Column({ type: "timestamp" }) 52 | timestamp: Date; 53 | } 54 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from "dotenv"; 2 | import express from "express"; 3 | import "reflect-metadata"; 4 | import { createConnection } from "typeorm"; 5 | import { pagination } from "typeorm-pagination"; 6 | import roleRouter from "./routes/RoleRouters"; 7 | import usersRouter from "./routes/UserRoutes"; 8 | 9 | dotenv.config(); 10 | 11 | const PORT = process.env.PORT || 9000; 12 | 13 | const app = express(); 14 | 15 | app.use(express.urlencoded({ extended: true })); 16 | app.use(express.json()); 17 | 18 | app.use(pagination); 19 | 20 | app.use("/role", roleRouter); 21 | app.use("/user", usersRouter); 22 | app.get("/", (_, res) => { 23 | res.status(200).json({ 24 | success: true, 25 | message: 26 | "You are on node-typescript-boilderplate. You should not have further access from here.", 27 | }); 28 | }); 29 | 30 | createConnection() 31 | .then(async () => { 32 | app.listen(PORT, () => { 33 | console.log(`CONNECTED TO DB AND SERVER STARTED ON PORT ${PORT}`); 34 | }); 35 | }) 36 | .catch((error) => console.log(error)); 37 | -------------------------------------------------------------------------------- /src/middlewares/Auth.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Response } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | import { RequestFailed } from "../response/RequestFailedResponse"; 4 | import { InternalServerError } from "../response/InternalServerErrorResponse"; 5 | import { AuthRequest } from "./AuthRequestContext"; 6 | 7 | interface JWT_DECODE { 8 | id: number; 9 | username: string; 10 | iat: number; 11 | exp: number; 12 | } 13 | 14 | export const Auth = (req: AuthRequest, res: Response, next: NextFunction) => { 15 | try { 16 | const token = req.headers["authorization"]?.split(" ")[1]; 17 | if (!token) { 18 | return RequestFailed(res, 404, "Unauthorized / no token found"); 19 | } else { 20 | const data = jwt.verify(token, process.env.TOKEN_SECRET!) as JWT_DECODE; 21 | req.userId = data.id; 22 | next(); 23 | } 24 | } catch (error) { 25 | return InternalServerError(res, error); 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /src/middlewares/AuthRequestContext.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | 3 | export type AuthRequest = Request & { userId?: number }; 4 | -------------------------------------------------------------------------------- /src/middlewares/IsAdmin.ts: -------------------------------------------------------------------------------- 1 | import { RequestFailed } from "./../response/RequestFailedResponse"; 2 | import { User } from "./../entity/User"; 3 | import { InternalServerError } from "./../response/InternalServerErrorResponse"; 4 | import { NextFunction, Response } from "express"; 5 | import { AuthRequest } from "./AuthRequestContext"; 6 | 7 | export const isAdmin = async ( 8 | req: AuthRequest, 9 | res: Response, 10 | next: NextFunction 11 | ) => { 12 | try { 13 | const userId = req.userId; 14 | const user = await User.findOne(userId, { relations: ["role"] }); 15 | 16 | if (!user || user.role.name.toLowerCase() !== "admin") { 17 | return RequestFailed(res, 403, "Unathorized Request"); 18 | } 19 | next(); 20 | } catch (error) { 21 | return InternalServerError(res, error); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /src/response/InternalServerErrorResponse.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | 3 | export const InternalServerError = (res: Response, error: any) => { 4 | console.log(error); 5 | console.log(error.message); 6 | 7 | if (error.code === "ER_DUP_ENTRY") { 8 | res.status(409).json({ 9 | success: false, 10 | message: error.sqlMessage, 11 | }); 12 | } else { 13 | res.status(500).json({ 14 | success: false, 15 | message: error.message, 16 | }); 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/response/LoginResponse.ts: -------------------------------------------------------------------------------- 1 | import { User } from "src/entity/User"; 2 | import { classToPlain } from "class-transformer"; 3 | 4 | export const LoginResponse = ( 5 | token: string, 6 | refreshToken: string, 7 | user: User 8 | ) => { 9 | const plainUser = classToPlain(user); 10 | 11 | return { 12 | token, 13 | refreshToken, 14 | user: plainUser, 15 | }; 16 | }; 17 | -------------------------------------------------------------------------------- /src/response/RequestFailedResponse.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | 3 | export const RequestFailed = ( 4 | res: Response, 5 | code: number, 6 | error: string, 7 | id?: number | string 8 | ) => { 9 | let composeMessage = ""; 10 | 11 | if (code === 400) { 12 | composeMessage = `${error} cannot be null`; 13 | } else if (code === 404) { 14 | if (id) { 15 | composeMessage = `${error} not found with id ${id}`; 16 | } else { 17 | composeMessage = `${error} not found`; 18 | } 19 | } else if (code === 401 || code === 403) { 20 | composeMessage = error; 21 | } 22 | res.status(code).json({ 23 | success: false, 24 | message: composeMessage, 25 | }); 26 | }; 27 | -------------------------------------------------------------------------------- /src/routes/RoleRouters.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { getAllRole, createRole } from "../controllers/RoleController"; 3 | 4 | let router = express.Router(); 5 | 6 | router.get("/getall", getAllRole); 7 | 8 | router.post("/create", createRole); 9 | 10 | 11 | export = router; 12 | -------------------------------------------------------------------------------- /src/routes/UserRoutes.ts: -------------------------------------------------------------------------------- 1 | import * as express from "express"; 2 | import { 3 | createUser, 4 | getAllUsers, 5 | getUserById, 6 | updateUser, 7 | login, 8 | } from "../controllers/UserController"; 9 | import { Auth } from "./../middlewares/Auth"; 10 | 11 | let router = express.Router(); 12 | 13 | router.get(`/list`, getAllUsers); 14 | router.get(``, Auth, getUserById); 15 | router.post(`/create`, createUser); 16 | router.patch(`/update`, Auth, updateUser); 17 | router.post(`/login`, login); 18 | 19 | export = router; 20 | -------------------------------------------------------------------------------- /src/types/RoleType.ts: -------------------------------------------------------------------------------- 1 | export enum RoleType { 2 | user = "user", 3 | admin = "admin", 4 | } 5 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "lib": [ 6 | "dom", 7 | "es6", 8 | "es2017", 9 | "esnext.asynciterable" 10 | ], 11 | "sourceMap": true, 12 | "outDir": "./dist", 13 | "moduleResolution": "node", 14 | "removeComments": true, 15 | "noImplicitAny": true, 16 | "strictNullChecks": true, 17 | "strictFunctionTypes": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "allowSyntheticDefaultImports": true, 24 | "esModuleInterop": true, 25 | "emitDecoratorMetadata": true, 26 | "experimentalDecorators": true, 27 | "resolveJsonModule": true, 28 | "baseUrl": "." 29 | }, 30 | "exclude": [ 31 | "node_modules" 32 | ], 33 | "include": [ 34 | "./src/**/*.tsx", 35 | "./src/**/*.ts" 36 | ] 37 | } --------------------------------------------------------------------------------