├── .dockerignore ├── .github └── workflows │ ├── release.yml │ └── tests.yml ├── .gitignore ├── Dockerfile ├── Dockerfile.dev ├── LICENSE ├── README.md ├── __mocks__ └── typeorm.ts ├── docker-compose.yml ├── jest.config.js ├── package-lock.json ├── package.json ├── public └── .gitkeep ├── src ├── config │ └── database.ts ├── controllers │ ├── comment.controller.test.ts │ ├── comment.controller.ts │ ├── ping.controller.test.ts │ ├── ping.controller.ts │ ├── post.controller.test.ts │ ├── post.controller.ts │ ├── user.controller.test.ts │ └── user.controller.ts ├── index.ts ├── models │ ├── comment.ts │ ├── index.ts │ ├── post.ts │ └── user.ts ├── repositories │ ├── comment.repository.test.ts │ ├── comment.repository.ts │ ├── post.repository.test.ts │ ├── post.repository.ts │ ├── user.repository.test.ts │ └── user.repository.ts └── routes │ ├── comment.router.ts │ ├── index.ts │ ├── post.router.ts │ └── user.router.ts ├── test └── utils │ └── generate.ts ├── tsconfig.json └── tsoa.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Build and Release 2 | on: 3 | push: 4 | tags: 5 | - "v*.*.*" 6 | 7 | jobs: 8 | docker: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout code 12 | uses: actions/checkout@v2 13 | - name: Get release tag 14 | id: get_version 15 | uses: battila7/get-version-action@v2 16 | - name: Login to GitHub Packages Docker Registry 17 | uses: docker/login-action@v1 18 | with: 19 | registry: docker.pkg.github.com 20 | username: ${{ github.actor }} 21 | password: ${{ secrets.GITHUB_TOKEN }} 22 | - name: Login to DockerHub 23 | uses: docker/login-action@v1 24 | with: 25 | username: ${{ secrets.DOCKERHUB_USERNAME }} 26 | password: ${{ secrets.DOCKERHUB_TOKEN }} 27 | - name: Build and Push 28 | uses: docker/build-push-action@v2 29 | with: 30 | context: . 31 | push: true 32 | tags: | 33 | docker.pkg.github.com/${{ github.repository }}/express-typescript:latest 34 | docker.pkg.github.com/${{ github.repository }}/express-typescript:${{ steps.get_version.outputs.version-without-v }} 35 | rsbh/express-typescript:latest 36 | rsbh/express-typescript:${{ steps.get_version.outputs.version-without-v }} 37 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | node-version: [10.x, 12.x, 14.x, 15.x] 11 | 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Use Node.js ${{ matrix.node-version }} 15 | uses: actions/setup-node@v1 16 | with: 17 | node-version: ${{ matrix.node-version }} 18 | - run: npm ci 19 | - run: npm test 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build 107 | pgdata -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine AS builder 2 | WORKDIR /app 3 | COPY package*.json ./ 4 | RUN npm install 5 | COPY . . 6 | RUN npm run build 7 | 8 | FROM node:16-alpine AS server 9 | WORKDIR /app 10 | COPY package* ./ 11 | RUN npm install --production 12 | COPY --from=builder ./app/public ./public 13 | COPY --from=builder ./app/build ./build 14 | EXPOSE 8000 15 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /Dockerfile.dev: -------------------------------------------------------------------------------- 1 | FROM node:16 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 8000 12 | 13 | CMD ["npm", "run", "dev"] 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rishabh Mishra 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-typescript 2 | 3 | Source code for the **Building REST API with Express, TypeScript** blog post series 4 | 5 | ## Post Links 6 | 7 | 1. [Building REST API with Express, TypeScript and Swagger](https://rsbh.dev/blog/rest-api-with-express-typescript) 8 | 2. [Building REST API with Express, TypeScript - Part 2: Docker Setup](https://rsbh.dev/blog/rest-api-express-typescript-docker) 9 | 3. [Building REST API with Express, TypeScript - Part 3: PostgreSQL and Typeorm](https://rsbh.dev/blog/rest-api-express-postgres-typeorm) 10 | 4. [Building REST API with Express, TypeScript - Part 4: Jest and unit testing](https://rsbh.dev/blog/rest-api-express-typescript-jest-testing) 11 | 12 | ## Build from source 13 | 14 | 1. Clone the repo 15 | 16 | ```sh 17 | git clone git@github.com:rsbh/express-typescript.git 18 | cd express-typescript 19 | ``` 20 | 21 | 2. Install dependencies. 22 | 23 | ```sh 24 | npm install 25 | ``` 26 | 27 | 3. Build the production server. 28 | 29 | ```sh 30 | npm build 31 | ``` 32 | 33 | 4. Run the server. 34 | ```sh 35 | npm start 36 | ``` 37 | 38 | ## Build Docker image locally 39 | 40 | ```sh 41 | docker build -t express-typescript . 42 | ``` 43 | 44 | ## Run tests 45 | 46 | ```sh 47 | npm test 48 | ``` 49 | -------------------------------------------------------------------------------- /__mocks__/typeorm.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getRepository: jest.fn().mockReturnValue({ 3 | find: jest.fn(), 4 | save: jest.fn(), 5 | findOne: jest.fn() 6 | }), 7 | PrimaryGeneratedColumn: jest.fn(), 8 | Column: jest.fn(), 9 | Entity: jest.fn(), 10 | ManyToOne: jest.fn(), 11 | OneToMany: jest.fn(), 12 | JoinColumn: jest.fn(), 13 | CreateDateColumn: jest.fn(), 14 | UpdateDateColumn: jest.fn() 15 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | db: 5 | image: postgres:12 6 | environment: 7 | - POSTGRES_DB=express-ts 8 | - POSTGRES_USER=postgres 9 | - POSTGRES_PASSWORD=postgres 10 | volumes: 11 | - ./pgdata:/var/lib/postgresql/data 12 | ports: 13 | - 5432:5432 14 | healthcheck: 15 | test: ["CMD-SHELL", "pg_isready -U postgres"] 16 | interval: 30s 17 | timeout: 30s 18 | retries: 3 19 | app: 20 | build: 21 | context: . 22 | dockerfile: Dockerfile.dev 23 | volumes: 24 | - ./src:/app/src 25 | ports: 26 | - "8000:8000" 27 | depends_on: 28 | db: 29 | condition: service_healthy 30 | environment: 31 | - POSTGRES_DB=express-ts 32 | - POSTGRES_USER=postgres 33 | - POSTGRES_PASSWORD=postgres 34 | - POSTGRES_HOST=db 35 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | moduleNameMapper: { 5 | "test/(.*)": "/test/$1", 6 | }, 7 | collectCoverage: true, 8 | collectCoverageFrom: [ 9 | "src/**/*.{js,ts}", 10 | ] 11 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-typescript", 3 | "version": "1.1.1", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node build/index.js", 8 | "predev": "npm run swagger", 9 | "prebuild": "npm run swagger", 10 | "build": "tsc", 11 | "dev": "concurrently \"nodemon\" \"nodemon -x tsoa spec\"", 12 | "swagger": "tsoa spec", 13 | "test": "jest" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/rsbh/express-typescript.git" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/rsbh/express-typescript/issues" 24 | }, 25 | "homepage": "https://github.com/rsbh/express-typescript#readme", 26 | "devDependencies": { 27 | "@types/express": "^4.17.13", 28 | "@types/faker": "^5.1.5", 29 | "@types/jest": "^27.4.1", 30 | "@types/morgan": "^1.9.3", 31 | "@types/node": "^17.0.21", 32 | "@types/swagger-ui-express": "^4.1.3", 33 | "concurrently": "^7.0.0", 34 | "faker": "^5.1.0", 35 | "jest": "^27.5.1", 36 | "nodemon": "^2.0.15", 37 | "ts-jest": "^27.1.3", 38 | "ts-node": "^10.6.0", 39 | "typescript": "^4.6.2" 40 | }, 41 | "dependencies": { 42 | "express": "^4.17.3", 43 | "morgan": "^1.10.0", 44 | "pg": "^8.7.3", 45 | "reflect-metadata": "^0.1.13", 46 | "swagger-ui-express": "^4.3.0", 47 | "tsoa": "^3.2.1", 48 | "typeorm": "^0.2.44" 49 | }, 50 | "nodemonConfig": { 51 | "watch": [ 52 | "src" 53 | ], 54 | "ext": "ts", 55 | "exec": "ts-node src/index.ts" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsbh/express-typescript/71d7739ba3e83fdd3c24fbd736165fcdb3ca5609/public/.gitkeep -------------------------------------------------------------------------------- /src/config/database.ts: -------------------------------------------------------------------------------- 1 | import {ConnectionOptions} from 'typeorm' 2 | import {User, Post, Comment} from '../models' 3 | 4 | const config : ConnectionOptions = { 5 | type: "postgres", 6 | host: process.env.POSTGRES_HOST || "localhost", 7 | port: Number(process.env.POSTGRES_PORT) || 5432, 8 | username: process.env.POSTGRES_USER || "postgres", 9 | password: process.env.POSTGRES_PASSWORD || "postgres", 10 | database: process.env.POSTGRES_DB || "postgres", 11 | entities: [User, Post, Comment], 12 | synchronize: true, 13 | } 14 | 15 | export default config -------------------------------------------------------------------------------- /src/controllers/comment.controller.test.ts: -------------------------------------------------------------------------------- 1 | import CommentController from './comment.controller' 2 | import * as CommentRepository from '../repositories/comment.repository' 3 | import {generateCommentsData, generateCommentPayload, generateCommentData} from 'test/utils/generate' 4 | 5 | afterEach(() => { 6 | jest.resetAllMocks() 7 | }) 8 | 9 | describe("CommentController", () => { 10 | describe("getComments", () => { 11 | test("should return empty array", async () => { 12 | const spy = jest.spyOn(CommentRepository, 'getComments').mockResolvedValueOnce([]) 13 | const controller = new CommentController(); 14 | const comments = await controller.getComments(); 15 | expect(comments).toEqual([]) 16 | expect(spy).toHaveBeenCalledWith() 17 | expect(spy).toHaveBeenCalledTimes(1) 18 | }) 19 | 20 | test("should return comments list", async () => { 21 | const commentsData = generateCommentsData(2) 22 | const spy = jest.spyOn(CommentRepository, 'getComments').mockResolvedValueOnce(commentsData) 23 | const controller = new CommentController(); 24 | const comments = await controller.getComments(); 25 | expect(comments).toEqual(commentsData) 26 | expect(spy).toHaveBeenCalledWith() 27 | expect(spy).toHaveBeenCalledTimes(1) 28 | }) 29 | }) 30 | 31 | describe("createComment", () => { 32 | test("should add comment to the database", async () => { 33 | const payload = generateCommentPayload() 34 | const commentData = generateCommentData(payload) 35 | const spy = jest.spyOn(CommentRepository, 'createComment').mockResolvedValueOnce(commentData) 36 | const controller = new CommentController(); 37 | const comment = await controller.createComment(payload); 38 | expect(comment).toMatchObject(payload) 39 | expect(comment).toEqual(commentData) 40 | expect(spy).toHaveBeenCalledWith(payload) 41 | expect(spy).toHaveBeenCalledTimes(1) 42 | }) 43 | }) 44 | 45 | describe("getComment", () => { 46 | test("should return comment from the database", async () => { 47 | const id = 1 48 | const commentData = generateCommentData({id}) 49 | const spy = jest.spyOn(CommentRepository, 'getComment').mockResolvedValueOnce(commentData) 50 | const controller = new CommentController(); 51 | const comment = await controller.getComment(id.toString()); 52 | expect(comment).toEqual(commentData) 53 | expect(comment?.id).toBe(id) 54 | expect(spy).toHaveBeenCalledWith(id) 55 | expect(spy).toHaveBeenCalledTimes(1) 56 | }) 57 | 58 | test("should return null if comment not found", async () => { 59 | const id = 1 60 | const spy = jest.spyOn(CommentRepository, 'getComment').mockResolvedValueOnce(null) 61 | const controller = new CommentController(); 62 | const comment = await controller.getComment(id.toString()); 63 | expect(comment).toBeNull() 64 | expect(spy).toHaveBeenCalledWith(id) 65 | expect(spy).toHaveBeenCalledTimes(1) 66 | }) 67 | }) 68 | }) -------------------------------------------------------------------------------- /src/controllers/comment.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Route, Tags, Post, Body, Path } from "tsoa"; 2 | import {Comment} from '../models' 3 | import { getComments, ICommentPayload, createComment, getComment } from "../repositories/comment.repository"; 4 | 5 | @Route("comments") 6 | @Tags("Comment") 7 | export default class CommentController { 8 | @Get("/") 9 | public async getComments(): Promise> { 10 | return getComments() 11 | } 12 | 13 | @Post("/") 14 | public async createComment(@Body() body: ICommentPayload): Promise { 15 | return createComment(body) 16 | } 17 | 18 | @Get("/:id") 19 | public async getComment(@Path() id: string): Promise { 20 | return getComment(Number(id)) 21 | } 22 | } -------------------------------------------------------------------------------- /src/controllers/ping.controller.test.ts: -------------------------------------------------------------------------------- 1 | import PingController from './ping.controller' 2 | 3 | test("should return pong message", async () => { 4 | const controller = new PingController(); 5 | const response = await controller.getMessage(); 6 | expect(response.message).toBe("pong") 7 | }) -------------------------------------------------------------------------------- /src/controllers/ping.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Route } from "tsoa"; 2 | 3 | interface PingResponse { 4 | message: string; 5 | } 6 | 7 | @Route("ping") 8 | export default class PingController { 9 | @Get("/") 10 | public async getMessage(): Promise { 11 | return { 12 | message: "pong", 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/controllers/post.controller.test.ts: -------------------------------------------------------------------------------- 1 | import PostController from './post.controller' 2 | import * as PostRepository from '../repositories/post.repository' 3 | import {generatePostsData, generatePostPayload, generatePostData} from 'test/utils/generate' 4 | 5 | afterEach(() => { 6 | jest.resetAllMocks() 7 | }) 8 | 9 | describe("PostController", () => { 10 | describe("getPosts", () => { 11 | test("should return empty array", async () => { 12 | const spy = jest.spyOn(PostRepository, 'getPosts').mockResolvedValueOnce([]) 13 | const controller = new PostController(); 14 | const posts = await controller.getPosts(); 15 | expect(posts).toEqual([]) 16 | expect(spy).toHaveBeenCalledWith() 17 | expect(spy).toHaveBeenCalledTimes(1) 18 | }) 19 | 20 | test("should return posts list", async () => { 21 | const postsData = generatePostsData(2) 22 | const spy = jest.spyOn(PostRepository, 'getPosts').mockResolvedValueOnce(postsData) 23 | const controller = new PostController(); 24 | const posts = await controller.getPosts(); 25 | expect(posts).toEqual(postsData) 26 | expect(spy).toHaveBeenCalledWith() 27 | expect(spy).toHaveBeenCalledTimes(1) 28 | }) 29 | }) 30 | 31 | describe("createPost", () => { 32 | test("should add post to the database", async () => { 33 | 34 | const payload = generatePostPayload() 35 | const postData = generatePostData(payload) 36 | const spy = jest.spyOn(PostRepository, 'createPost').mockResolvedValueOnce(postData) 37 | const controller = new PostController(); 38 | const post = await controller.createPost(payload); 39 | expect(post).toMatchObject(payload) 40 | expect(post).toEqual(postData) 41 | expect(spy).toHaveBeenCalledWith(payload) 42 | expect(spy).toHaveBeenCalledTimes(1) 43 | }) 44 | }) 45 | 46 | describe("getPost", () => { 47 | test("should return post from the database", async () => { 48 | const id = 1 49 | const postData = generatePostData({id}) 50 | const spy = jest.spyOn(PostRepository, 'getPost').mockResolvedValueOnce(postData) 51 | const controller = new PostController(); 52 | const post = await controller.getPost(id.toString()); 53 | expect(post).toEqual(postData) 54 | expect(post?.id).toBe(id) 55 | expect(spy).toHaveBeenCalledWith(id) 56 | expect(spy).toHaveBeenCalledTimes(1) 57 | }) 58 | 59 | test("should return null if post not found", async () => { 60 | const id = 1 61 | const spy = jest.spyOn(PostRepository, 'getPost').mockResolvedValueOnce(null) 62 | const controller = new PostController(); 63 | const post = await controller.getPost(id.toString()); 64 | expect(post).toBeNull() 65 | expect(spy).toHaveBeenCalledWith(id) 66 | expect(spy).toHaveBeenCalledTimes(1) 67 | }) 68 | }) 69 | }) -------------------------------------------------------------------------------- /src/controllers/post.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Route, Tags, Post as PostMethod, Body, Path } from "tsoa"; 2 | import {Post} from '../models' 3 | import { createPost, getPosts, IPostPayload, getPost } from "../repositories/post.repository"; 4 | 5 | @Route("posts") 6 | @Tags("Post") 7 | export default class PostController { 8 | @Get("/") 9 | public async getPosts(): Promise> { 10 | return getPosts() 11 | } 12 | 13 | @PostMethod("/") 14 | public async createPost(@Body() body: IPostPayload): Promise { 15 | return createPost(body) 16 | } 17 | 18 | @Get("/:id") 19 | public async getPost(@Path() id: string): Promise { 20 | return getPost(Number(id)) 21 | } 22 | } -------------------------------------------------------------------------------- /src/controllers/user.controller.test.ts: -------------------------------------------------------------------------------- 1 | import UserController from './user.controller' 2 | import * as UserRepository from '../repositories/user.repository' 3 | import {generateUsersData, generateUserPayload, generateUserData} from 'test/utils/generate' 4 | 5 | afterEach(() => { 6 | jest.resetAllMocks() 7 | }) 8 | 9 | describe("UserController", () => { 10 | describe("getUsers", () => { 11 | test("should return empty array", async () => { 12 | const spy = jest.spyOn(UserRepository, 'getUsers').mockResolvedValueOnce([]) 13 | const controller = new UserController(); 14 | const users = await controller.getUsers(); 15 | expect(users).toEqual([]) 16 | expect(spy).toHaveBeenCalledWith() 17 | expect(spy).toHaveBeenCalledTimes(1) 18 | }) 19 | 20 | test("should return user list", async () => { 21 | const usersData = generateUsersData(2) 22 | const spy = jest.spyOn(UserRepository, 'getUsers').mockResolvedValueOnce(usersData) 23 | const controller = new UserController(); 24 | const users = await controller.getUsers(); 25 | expect(users).toEqual(usersData) 26 | expect(spy).toHaveBeenCalledWith() 27 | expect(spy).toHaveBeenCalledTimes(1) 28 | }) 29 | }) 30 | 31 | describe("addUser", () => { 32 | test("should add user to the database", async () => { 33 | const payload = generateUserPayload() 34 | const userData = generateUserData(payload) 35 | const spy = jest.spyOn(UserRepository, 'createUser').mockResolvedValueOnce(userData) 36 | const controller = new UserController(); 37 | const user = await controller.createUser(payload); 38 | expect(user).toMatchObject(payload) 39 | expect(user).toEqual(userData) 40 | expect(spy).toHaveBeenCalledWith(payload) 41 | expect(spy).toHaveBeenCalledTimes(1) 42 | }) 43 | }) 44 | 45 | describe("getUser", () => { 46 | test("should return user from the database", async () => { 47 | const id = 1 48 | const userData = generateUserData({id}) 49 | const spy = jest.spyOn(UserRepository, 'getUser').mockResolvedValueOnce(userData) 50 | const controller = new UserController(); 51 | const user = await controller.getUser(id.toString()); 52 | expect(user).toEqual(userData) 53 | expect(user?.id).toBe(id) 54 | expect(spy).toHaveBeenCalledWith(id) 55 | expect(spy).toHaveBeenCalledTimes(1) 56 | }) 57 | 58 | test("should return null if user not found", async () => { 59 | const id = 1 60 | const spy = jest.spyOn(UserRepository, 'getUser').mockResolvedValueOnce(null) 61 | const controller = new UserController(); 62 | const user = await controller.getUser(id.toString()); 63 | expect(user).toBeNull() 64 | expect(spy).toHaveBeenCalledWith(id) 65 | expect(spy).toHaveBeenCalledTimes(1) 66 | }) 67 | }) 68 | }) -------------------------------------------------------------------------------- /src/controllers/user.controller.ts: -------------------------------------------------------------------------------- 1 | import { Get, Route, Tags, Post, Body, Path } from "tsoa"; 2 | import {User} from '../models' 3 | import {getUsers, createUser, IUserPayload, getUser} from '../repositories/user.repository' 4 | 5 | @Route("users") 6 | @Tags("User") 7 | export default class UserController { 8 | @Get("/") 9 | public async getUsers(): Promise> { 10 | return getUsers() 11 | } 12 | 13 | @Post("/") 14 | public async createUser(@Body() body: IUserPayload): Promise { 15 | return createUser(body) 16 | } 17 | 18 | @Get("/:id") 19 | public async getUser(@Path() id: string): Promise { 20 | return getUser(Number(id)) 21 | } 22 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import { createConnection } from "typeorm"; 3 | import express, { Application } from "express"; 4 | import morgan from "morgan"; 5 | import swaggerUi from "swagger-ui-express"; 6 | 7 | import Router from "./routes"; 8 | import dbConfig from "./config/database"; 9 | 10 | const PORT = process.env.PORT || 8000; 11 | 12 | const app: Application = express(); 13 | 14 | app.use(express.json()); 15 | app.use(morgan("tiny")); 16 | app.use(express.static("public")); 17 | 18 | app.use( 19 | "/docs", 20 | swaggerUi.serve, 21 | swaggerUi.setup(undefined, { 22 | swaggerOptions: { 23 | url: "/swagger.json", 24 | }, 25 | }) 26 | ); 27 | 28 | app.use(Router); 29 | 30 | createConnection(dbConfig) 31 | .then(() => { 32 | app.listen(PORT, () => { 33 | console.log("Server is running on port", PORT); 34 | }); 35 | }) 36 | .catch((err) => { 37 | console.log("Unable to connect to db", err); 38 | process.exit(1); 39 | }); 40 | -------------------------------------------------------------------------------- /src/models/comment.ts: -------------------------------------------------------------------------------- 1 | import {Entity, PrimaryGeneratedColumn, Column, ManyToOne, CreateDateColumn, UpdateDateColumn, JoinColumn} from "typeorm"; 2 | import { Post } from "./post"; 3 | import { User } from "./user"; 4 | 5 | @Entity() 6 | export class Comment { 7 | 8 | @PrimaryGeneratedColumn() 9 | id!: number; 10 | 11 | @Column({ 12 | type: 'text' 13 | }) 14 | content!: string; 15 | 16 | @Column({ nullable: true }) 17 | userId!: number; 18 | @ManyToOne(_type => User, (user: User) => user.comments) 19 | @JoinColumn() 20 | user!: User; 21 | 22 | @Column({ nullable: true }) 23 | postId!: number; 24 | @ManyToOne(_type => Post, (post: Post) => post.comments) 25 | @JoinColumn() 26 | post!: Post; 27 | 28 | @CreateDateColumn() 29 | createdAt!: Date; 30 | 31 | @UpdateDateColumn() 32 | updatedAt!: Date; 33 | } -------------------------------------------------------------------------------- /src/models/index.ts: -------------------------------------------------------------------------------- 1 | import {User} from './user' 2 | import {Post} from './post' 3 | import {Comment} from './comment' 4 | export {User, Post, Comment} -------------------------------------------------------------------------------- /src/models/post.ts: -------------------------------------------------------------------------------- 1 | import {Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany,CreateDateColumn, UpdateDateColumn, JoinColumn} from "typeorm"; 2 | import { Comment } from "./comment"; 3 | import { User } from "./user"; 4 | 5 | @Entity() 6 | export class Post { 7 | 8 | @PrimaryGeneratedColumn() 9 | id!: number; 10 | 11 | @Column() 12 | title!: string; 13 | 14 | @Column({ 15 | type: 'text' 16 | }) 17 | content!: string; 18 | 19 | @Column({ nullable: true }) 20 | userId!: number; 21 | @ManyToOne(_type => User, (user: User) => user.posts) 22 | @JoinColumn() 23 | user!: User; 24 | 25 | @OneToMany(_type=> Comment, (comment: Comment) => comment.post) 26 | comments!: Array; 27 | 28 | @CreateDateColumn() 29 | createdAt!: Date; 30 | 31 | @UpdateDateColumn() 32 | updatedAt!: Date; 33 | } -------------------------------------------------------------------------------- /src/models/user.ts: -------------------------------------------------------------------------------- 1 | import {Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, OneToMany, UpdateDateColumn} from "typeorm"; 2 | import { Post } from "./post"; 3 | import {Comment} from './comment'; 4 | 5 | @Entity() 6 | export class User { 7 | 8 | @PrimaryGeneratedColumn() 9 | id!: number; 10 | 11 | @Column() 12 | firstName!: string; 13 | 14 | @Column() 15 | lastName!: string; 16 | 17 | @Column() 18 | email!: string; 19 | 20 | @OneToMany(_type => Post, (post: Post) => post.user) 21 | posts!: Array 22 | 23 | @OneToMany(_type=> Comment, (comment: Comment) => comment.user) 24 | comments!: Array; 25 | 26 | @CreateDateColumn() 27 | createdAt!: Date; 28 | 29 | @UpdateDateColumn() 30 | updatedAt!: Date; 31 | } -------------------------------------------------------------------------------- /src/repositories/comment.repository.test.ts: -------------------------------------------------------------------------------- 1 | import * as CommentRepository from './comment.repository' 2 | import {getRepository} from 'typeorm' 3 | import { mocked } from 'ts-jest/utils' 4 | import {generateCommentsData, generateCommentPayload, generateCommentData} from 'test/utils/generate' 5 | 6 | jest.mock('typeorm'); 7 | 8 | const mockedGetRepo = mocked(getRepository({})) 9 | beforeEach(() => { 10 | mockedGetRepo.find.mockClear() 11 | mockedGetRepo.findOne.mockClear() 12 | mockedGetRepo.save.mockClear() 13 | }) 14 | 15 | describe("CommentRepository", () => { 16 | describe("getComments", () => { 17 | test("should return empty array", async () => { 18 | mockedGetRepo.find.mockResolvedValue([]) 19 | const comments = await CommentRepository.getComments(); 20 | expect(comments).toEqual([]) 21 | expect(mockedGetRepo.find).toHaveBeenCalledWith() 22 | expect(mockedGetRepo.find).toHaveBeenCalledTimes(1) 23 | }) 24 | 25 | test("should return comments list", async () => { 26 | const commentsData = generateCommentsData(2) 27 | mockedGetRepo.find.mockResolvedValue(commentsData) 28 | const comments = await CommentRepository.getComments(); 29 | expect(comments).toEqual(commentsData) 30 | expect(mockedGetRepo.find).toHaveBeenCalledWith() 31 | expect(mockedGetRepo.find).toHaveBeenCalledTimes(1) 32 | }) 33 | }) 34 | 35 | describe("createComment", () => { 36 | test("should add comment to the database", async () => { 37 | const payload = generateCommentPayload() 38 | const commentData = generateCommentData(payload) 39 | mockedGetRepo.save.mockResolvedValue(commentData) 40 | const comment = await CommentRepository.createComment(payload); 41 | expect(comment).toMatchObject(payload) 42 | expect(comment).toEqual(commentData) 43 | expect(mockedGetRepo.save).toHaveBeenCalledWith(payload) 44 | expect(mockedGetRepo.save).toHaveBeenCalledTimes(1) 45 | }) 46 | }) 47 | 48 | describe("getComment", () => { 49 | test("should return comment from the database", async () => { 50 | const id = 1 51 | const commentData = generateCommentData({id}) 52 | mockedGetRepo.findOne.mockResolvedValue(commentData) 53 | const comment = await CommentRepository.getComment(id); 54 | expect(comment).toEqual(commentData) 55 | expect(comment?.id).toBe(id) 56 | expect(mockedGetRepo.findOne).toHaveBeenCalledWith({id}) 57 | expect(mockedGetRepo.findOne).toHaveBeenCalledTimes(1) 58 | }) 59 | 60 | test("should return null if comment not found", async () => { 61 | const id = 1 62 | mockedGetRepo.findOne.mockResolvedValue(null) 63 | const comment = await CommentRepository.getComment(id); 64 | expect(comment).toBeNull() 65 | expect(mockedGetRepo.findOne).toHaveBeenCalledWith({id}) 66 | expect(mockedGetRepo.findOne).toHaveBeenCalledTimes(1) 67 | }) 68 | }) 69 | }) -------------------------------------------------------------------------------- /src/repositories/comment.repository.ts: -------------------------------------------------------------------------------- 1 | import {getRepository} from "typeorm"; 2 | import {Comment} from '../models' 3 | 4 | export interface ICommentPayload { 5 | content: string; 6 | userId: number; 7 | postId: number; 8 | } 9 | 10 | export const getComments = async () :Promise> => { 11 | const commentRepository = getRepository(Comment); 12 | return commentRepository.find() 13 | } 14 | 15 | export const createComment = async (payload: ICommentPayload) :Promise => { 16 | const commentRepository = getRepository(Comment); 17 | const comment = new Comment() 18 | return commentRepository.save({ 19 | ...comment, 20 | ...payload 21 | }) 22 | } 23 | 24 | export const getComment = async (id: number) :Promise => { 25 | const commentRepository = getRepository(Comment); 26 | const comment = await commentRepository.findOne({id: id}) 27 | if (!comment) return null 28 | return comment 29 | } -------------------------------------------------------------------------------- /src/repositories/post.repository.test.ts: -------------------------------------------------------------------------------- 1 | import * as PostRepository from './post.repository' 2 | import {getRepository} from 'typeorm' 3 | import { mocked } from 'ts-jest/utils' 4 | import {generatePostsData, generatePostPayload, generatePostData} from 'test/utils/generate' 5 | 6 | jest.mock('typeorm'); 7 | 8 | const mockedGetRepo = mocked(getRepository({})) 9 | beforeEach(() => { 10 | mockedGetRepo.find.mockClear() 11 | mockedGetRepo.findOne.mockClear() 12 | mockedGetRepo.save.mockClear() 13 | }) 14 | 15 | describe("PostRepository", () => { 16 | describe("getPosts", () => { 17 | test("should return empty array", async () => { 18 | mockedGetRepo.find.mockResolvedValue([]) 19 | const posts = await PostRepository.getPosts(); 20 | expect(posts).toEqual([]) 21 | expect(mockedGetRepo.find).toHaveBeenCalledWith() 22 | expect(mockedGetRepo.find).toHaveBeenCalledTimes(1) 23 | }) 24 | 25 | test("should return posts list", async () => { 26 | const postsData = generatePostsData(2) 27 | mockedGetRepo.find.mockResolvedValue(postsData) 28 | const posts = await PostRepository.getPosts(); 29 | expect(posts).toEqual(postsData) 30 | expect(mockedGetRepo.find).toHaveBeenCalledWith() 31 | expect(mockedGetRepo.find).toHaveBeenCalledTimes(1) 32 | }) 33 | }) 34 | 35 | describe("createPost", () => { 36 | test("should add post to the database", async () => { 37 | const payload = generatePostPayload() 38 | const postData = generatePostData(payload) 39 | mockedGetRepo.save.mockResolvedValue(postData) 40 | const post = await PostRepository.createPost(payload); 41 | expect(post).toMatchObject(payload) 42 | expect(post).toEqual(postData) 43 | expect(mockedGetRepo.save).toHaveBeenCalledWith(payload) 44 | expect(mockedGetRepo.save).toHaveBeenCalledTimes(1) 45 | }) 46 | }) 47 | 48 | describe("getPost", () => { 49 | test("should return post from the database", async () => { 50 | const id = 1 51 | const postData = generatePostData({id}) 52 | mockedGetRepo.findOne.mockResolvedValue(postData) 53 | const post = await PostRepository.getPost(id); 54 | expect(post).toEqual(postData) 55 | expect(post?.id).toBe(id) 56 | expect(mockedGetRepo.findOne).toHaveBeenCalledWith({id}) 57 | expect(mockedGetRepo.findOne).toHaveBeenCalledTimes(1) 58 | }) 59 | 60 | test("should return null if post not found", async () => { 61 | const id = 1 62 | mockedGetRepo.findOne.mockResolvedValue(null) 63 | const post = await PostRepository.getPost(id); 64 | expect(post).toBeNull() 65 | expect(mockedGetRepo.findOne).toHaveBeenCalledWith({id}) 66 | expect(mockedGetRepo.findOne).toHaveBeenCalledTimes(1) 67 | }) 68 | }) 69 | }) -------------------------------------------------------------------------------- /src/repositories/post.repository.ts: -------------------------------------------------------------------------------- 1 | import {getRepository} from "typeorm"; 2 | import {Post} from '../models' 3 | 4 | export interface IPostPayload { 5 | title: string; 6 | content: string; 7 | userId: number; 8 | } 9 | 10 | export const getPosts = async () :Promise> => { 11 | const postRepository = getRepository(Post); 12 | return postRepository.find() 13 | } 14 | 15 | export const createPost = async (payload: IPostPayload) :Promise => { 16 | const postRepository = getRepository(Post); 17 | const post = new Post() 18 | return postRepository.save({ 19 | ...post, 20 | ...payload 21 | }) 22 | } 23 | 24 | export const getPost = async (id: number) :Promise => { 25 | const postRepository = getRepository(Post); 26 | const post = await postRepository.findOne({id: id}) 27 | if (!post) return null 28 | return post 29 | } -------------------------------------------------------------------------------- /src/repositories/user.repository.test.ts: -------------------------------------------------------------------------------- 1 | import * as UserRepository from './user.repository' 2 | import {getRepository} from 'typeorm' 3 | import { mocked } from 'ts-jest/utils' 4 | import {generateUsersData, generateUserPayload, generateUserData} from 'test/utils/generate' 5 | 6 | jest.mock('typeorm'); 7 | 8 | const mockedGetRepo = mocked(getRepository({})) 9 | beforeEach(() => { 10 | mockedGetRepo.find.mockClear() 11 | mockedGetRepo.findOne.mockClear() 12 | mockedGetRepo.save.mockClear() 13 | }) 14 | 15 | describe("UserRepository", () => { 16 | describe("getUsers", () => { 17 | test('should return empty array', async () => { 18 | mockedGetRepo.find.mockResolvedValue([]) 19 | const users = await UserRepository.getUsers(); 20 | expect(users).toEqual([]) 21 | expect(mockedGetRepo.find).toHaveBeenCalledWith() 22 | expect(mockedGetRepo.find).toHaveBeenCalledTimes(1) 23 | }) 24 | 25 | test("should return user list", async () => { 26 | const usersData = generateUsersData(2) 27 | mockedGetRepo.find.mockResolvedValue(usersData) 28 | const users = await UserRepository.getUsers(); 29 | expect(users).toEqual(usersData) 30 | expect(mockedGetRepo.find).toHaveBeenCalledWith() 31 | expect(mockedGetRepo.find).toHaveBeenCalledTimes(1) 32 | }) 33 | }) 34 | 35 | describe("addUser", () => { 36 | test("should add user to the database", async () => { 37 | const payload = generateUserPayload() 38 | const userData = generateUserData(payload) 39 | mockedGetRepo.save.mockResolvedValue(userData) 40 | const user = await UserRepository.createUser(payload); 41 | expect(user).toMatchObject(payload) 42 | expect(user).toEqual(userData) 43 | expect(mockedGetRepo.save).toHaveBeenCalledWith(payload) 44 | expect(mockedGetRepo.save).toHaveBeenCalledTimes(1) 45 | }) 46 | }) 47 | 48 | describe("getUser", () => { 49 | test("should return user from the database", async () => { 50 | const id = 1 51 | const userData = generateUserData({id}) 52 | mockedGetRepo.findOne.mockResolvedValue(userData) 53 | const user = await UserRepository.getUser(id) 54 | expect(user).toEqual(userData) 55 | expect(user?.id).toBe(id) 56 | expect(mockedGetRepo.findOne).toHaveBeenCalledWith({id}) 57 | expect(mockedGetRepo.findOne).toHaveBeenCalledTimes(1) 58 | }) 59 | 60 | test("should return null if user not found", async () => { 61 | const id = 1 62 | mockedGetRepo.findOne.mockResolvedValue(null) 63 | const user = await UserRepository.getUser(id) 64 | expect(user).toBeNull() 65 | expect(mockedGetRepo.findOne).toHaveBeenCalledWith({id}) 66 | expect(mockedGetRepo.findOne).toHaveBeenCalledTimes(1) 67 | }) 68 | }) 69 | }) -------------------------------------------------------------------------------- /src/repositories/user.repository.ts: -------------------------------------------------------------------------------- 1 | import {getRepository} from "typeorm"; 2 | import {User} from '../models' 3 | 4 | export interface IUserPayload { 5 | firstName: string; 6 | lastName: string; 7 | email: string 8 | } 9 | 10 | export const getUsers = async () :Promise> => { 11 | const userRepository = getRepository(User); 12 | return userRepository.find() 13 | } 14 | 15 | export const createUser = async (payload: IUserPayload) :Promise => { 16 | const userRepository = getRepository(User); 17 | const user = new User() 18 | return userRepository.save({ 19 | ...user, 20 | ...payload 21 | }) 22 | } 23 | 24 | export const getUser = async (id: number) :Promise => { 25 | const userRepository = getRepository(User); 26 | const user = await userRepository.findOne({id: id}) 27 | if (!user) return null 28 | return user 29 | } 30 | -------------------------------------------------------------------------------- /src/routes/comment.router.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import CommentController from '../controllers/comment.controller'; 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/", async (_req, res) => { 7 | const controller = new CommentController(); 8 | const response = await controller.getComments(); 9 | return res.send(response); 10 | }); 11 | 12 | router.post("/", async (req, res) => { 13 | const controller = new CommentController(); 14 | const response = await controller.createComment(req.body); 15 | return res.send(response); 16 | }); 17 | 18 | router.get("/:id", async (req, res) => { 19 | const controller = new CommentController(); 20 | const response = await controller.getComment(req.params.id); 21 | if (!response) res.status(404).send({message: "No comment found"}) 22 | return res.send(response); 23 | }); 24 | 25 | export default router; -------------------------------------------------------------------------------- /src/routes/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import PingController from "../controllers/ping.controller"; 3 | import PostRouter from "./post.router"; 4 | import UserRouter from "./user.router"; 5 | import CommentRouter from "./comment.router"; 6 | 7 | const router = express.Router(); 8 | 9 | router.get("/ping", async (_req, res) => { 10 | const controller = new PingController(); 11 | const response = await controller.getMessage(); 12 | return res.send(response); 13 | }); 14 | 15 | router.use("/users", UserRouter) 16 | router.use("/posts", PostRouter) 17 | router.use("/comments", CommentRouter) 18 | 19 | export default router; 20 | -------------------------------------------------------------------------------- /src/routes/post.router.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import PostController from '../controllers/post.controller'; 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/", async (_req, res) => { 7 | const controller = new PostController(); 8 | const response = await controller.getPosts(); 9 | return res.send(response); 10 | }); 11 | 12 | router.post("/", async (req, res) => { 13 | const controller = new PostController(); 14 | const response = await controller.createPost(req.body); 15 | return res.send(response); 16 | }); 17 | 18 | router.get("/:id", async (req, res) => { 19 | const controller = new PostController(); 20 | const response = await controller.getPost(req.params.id); 21 | if (!response) res.status(404).send({message: "No post found"}) 22 | return res.send(response); 23 | }); 24 | 25 | export default router; -------------------------------------------------------------------------------- /src/routes/user.router.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import UserController from "../controllers/user.controller"; 3 | 4 | const router = express.Router(); 5 | 6 | router.get("/", async (_req, res) => { 7 | const controller = new UserController(); 8 | const response = await controller.getUsers(); 9 | return res.send(response); 10 | }); 11 | 12 | router.post("/", async (req, res) => { 13 | const controller = new UserController(); 14 | const response = await controller.createUser(req.body); 15 | return res.send(response); 16 | }); 17 | 18 | router.get("/:id", async (req, res) => { 19 | const controller = new UserController(); 20 | const response = await controller.getUser(req.params.id); 21 | if (!response) res.status(404).send({message: "No user found"}) 22 | return res.send(response); 23 | }); 24 | 25 | export default router -------------------------------------------------------------------------------- /test/utils/generate.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker' 2 | import { User, Post } from '../../src/models'; 3 | 4 | export function generateUserData(overide = {}) { 5 | return { 6 | id: faker.random.number(), 7 | firstName: faker.name.firstName(), 8 | lastName: faker.name.lastName(), 9 | email: faker.internet.email(), 10 | posts: [], 11 | comments: [], 12 | createdAt: new Date(), 13 | updatedAt: new Date(), 14 | ...overide 15 | } 16 | } 17 | 18 | export function generateUsersData(n: number = 1) { 19 | return Array.from({ 20 | length: n 21 | }, (_, i) => { 22 | return generateUserData() 23 | }); 24 | } 25 | 26 | export function generateUserPayload() { 27 | return { 28 | firstName: faker.name.firstName(), 29 | lastName: faker.name.lastName(), 30 | email: faker.internet.email(), 31 | } 32 | } 33 | 34 | export function generatePostData(overide = {}) { 35 | return { 36 | id: faker.random.number(), 37 | title: faker.lorem.sentence(), 38 | content: faker.lorem.paragraph(), 39 | userId: faker.random.number(), 40 | comments: [], 41 | user: new User(), 42 | createdAt: new Date(), 43 | updatedAt: new Date(), 44 | ...overide 45 | } 46 | } 47 | 48 | export function generatePostsData(n: number = 1, overide = {}) { 49 | return Array.from({ 50 | length: n 51 | }, (_, i) => { 52 | return generatePostData(overide) 53 | }); 54 | } 55 | 56 | export function generatePostPayload() { 57 | return { 58 | title: faker.lorem.sentence(), 59 | content: faker.lorem.paragraph(), 60 | userId: faker.random.number(), 61 | } 62 | } 63 | 64 | export function generateCommentData(overide = {}) { 65 | return { 66 | id: faker.random.number(), 67 | content: faker.lorem.paragraph(), 68 | userId: faker.random.number(), 69 | user: new User(), 70 | postId: faker.random.number(), 71 | post: new Post(), 72 | createdAt: new Date(), 73 | updatedAt: new Date(), 74 | ...overide 75 | } 76 | } 77 | 78 | export function generateCommentsData(n: number = 1, overide = {}) { 79 | return Array.from({ 80 | length: n 81 | }, (_, i) => { 82 | return generateCommentData(overide) 83 | }); 84 | } 85 | 86 | 87 | export function generateCommentPayload() { 88 | return { 89 | content: faker.lorem.paragraph(), 90 | userId: faker.random.number(), 91 | postId: faker.random.number(), 92 | } 93 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "outDir": "./build", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "baseUrl": "./", 11 | "paths": { 12 | "test/*": ["test/*"] 13 | } 14 | }, 15 | "include": [ "src/**/*"], 16 | "exclude" : ["src/**/*.test.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /tsoa.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryFile": "src/index.ts", 3 | "noImplicitAdditionalProperties": "throw-on-extras", 4 | "spec": { 5 | "outputDirectory": "public", 6 | "specVersion": 3 7 | }, 8 | "ignore": ["**/node_modules/**"] 9 | } 10 | --------------------------------------------------------------------------------