Skip to content

Commit

Permalink
Merge pull request #22 from edwinhern/refactor/ServiceResponseStructure
Browse files Browse the repository at this point in the history
Refactor: Test structure and ServiceResponse Class
  • Loading branch information
edwinhern authored Jan 20, 2024
2 parents a8439f4 + 84477e2 commit 71e7be2
Show file tree
Hide file tree
Showing 26 changed files with 391 additions and 186 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
node-version: ${{ env.NODE_VERSION }}

- name: Install dependencies
run: npm install
run: npm ci

- name: Run build
run: npm run build
Expand All @@ -31,4 +31,4 @@ jobs:
run: npm run lint

- name: Check format
run: npm run check-format
run: npm run format
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ WORKDIR /usr/src/app
# Copy package.json and package-lock.json
COPY package*.json ./

# Install global and app dependencies
RUN npm install -g typescript tsx
RUN npm install
# Install app dependencies
RUN npm ci

# Bundle app source
COPY . .
Expand Down
12 changes: 4 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,31 +58,27 @@ Developed to streamline backend development, this boilerplate is your solution f
.
├── common
│ ├── middleware
│ │ ├── compressFilter.ts
│ │ ├── errorHandler.ts
│ │ ├── rateLimiter.ts
│ │ └── requestLogger.ts
│ ├── models
│ │ └── serviceResponse.ts
│ └── utils
│ ├── commonValidation.ts
│ ├── envConfig.ts
│ └── responseHandler.ts
│ └── httpHandlers.ts
├── index.ts
├── modules
│ ├── healthCheck
│ │ ├── healthCheckRoutes.ts
│ │ └── tests
│ │ └── healthCheckRoutes.test.ts
│ │ └── healthCheckRoutes.ts
│ └── user
│ ├── tests
│ │ └── userRoutes.test.ts
│ ├── userModel.ts
│ ├── userRepository.ts
│ ├── userRoutes.ts
│ └── userService.ts
└── server.ts
10 directories, 16 files
8 directories, 14 files
```

## 🤝 Feedback and Contributions
Expand Down
4 changes: 2 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const { compilerOptions } = require('./tsconfig.json');
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
roots: ['<rootDir>/src', '<rootDir>/tests'],
testMatch: ['**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)'],
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
prefix: '<rootDir>/',
Expand All @@ -19,6 +19,6 @@ module.exports = {
coverageDirectory: 'coverage',
testPathIgnorePatterns: ['/lib/', '/node_modules/', '/img/', '/dist/'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
modulePaths: ['src'],
modulePaths: ['<rootDir>/src'],
moduleDirectories: ['node_modules'],
};
90 changes: 29 additions & 61 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 12 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,37 @@
"description": "An Express.js boilerplate backend",
"main": "index.ts",
"scripts": {
"pretty": "pino-pretty",
"dev": "tsx watch --clear-screen=false src/index.ts | npm run pretty",
"build": "rimraf build && tsc",
"start": "NODE_ENV=production npm run build && tsx build/index.js | npm run pretty",
"lint": "eslint 'src/**/*.{ts,js}'",
"lint:fix": "eslint --fix --ext .ts,.js src/",
"format": "prettier --write 'src/**/*.{ts,js,json}'",
"check-format": "prettier --check 'src/**/*.{ts,js,json}'",
"ts:check": "tsc --noEmit",
"dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty",
"build": "rimraf build && tsc",
"start": "NODE_ENV=production npm run build && tsx build/src/index.js",
"lint": "eslint --fix --ext .ts,.js src/ tests/",
"format": "prettier --write 'src/**/*.{ts,js,json}' 'tests/**/*.{ts,js,json}'",
"test": "jest",
"prepare": "husky install",
"docker:build": "docker-compose build --no-cache",
"docker:start": "docker-compose up",
"docker:stop": "docker-compose down"
},
"dependencies": {
"compression": "^1.7.4",
"cors": "^2.8.5",
"crypto": "^1.0.1",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"pino-http": "^9.0.0"
"http-status-codes": "^2.3.0",
"lodash": "^4.17.21",
"pino-http": "^9.0.0",
"zod": "^3.22.4"
},
"devDependencies": {
"@tsconfig/node-lts-strictest-esm": "^18.12.1",
"@types/compression": "^1.7.5",
"@types/cors": "^2.8.17",
"@types/dotenv": "^8.2.0",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.11",
"@types/lodash": "^4.14.202",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
Expand All @@ -56,9 +55,8 @@
},
"lint-staged": {
"**/*.{ts,js}": [
"npm run lint:fix",
"npm run format",
"git add"
"npm run lint",
"npm run format"
],
"**/*.{ts,js,json,css,md}": "npm run format"
},
Expand Down
14 changes: 0 additions & 14 deletions src/common/middleware/compressFilter.ts

This file was deleted.

5 changes: 3 additions & 2 deletions src/common/middleware/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { ErrorRequestHandler, RequestHandler } from 'express';
import { StatusCodes } from 'http-status-codes';

const unexpectedRequest: RequestHandler = (_req, res) => {
res.sendStatus(404);
res.sendStatus(StatusCodes.NOT_FOUND);
};

const addErrorToRequestLog: ErrorRequestHandler = (err, _req, res, next) => {
Expand All @@ -10,7 +11,7 @@ const addErrorToRequestLog: ErrorRequestHandler = (err, _req, res, next) => {
};

const defaultErrorRequestHandler: ErrorRequestHandler = (_err, _req, res) => {
res.sendStatus(500);
res.sendStatus(StatusCodes.INTERNAL_SERVER_ERROR);
};

export default () => [unexpectedRequest, addErrorToRequestLog, defaultErrorRequestHandler];
19 changes: 13 additions & 6 deletions src/common/models/serviceResponse.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
export class ServiceResponse<T> {
import _ from 'lodash';

export enum ResponseStatus {
Success,
Failed,
}

export class ServiceResponse<T = null> {
success: boolean;
message: string;
responseObject: T | null;
errors?: unknown;
responseObject: T;
statusCode: number;

constructor(success: boolean, message: string, responseObject: T | null, errors?: unknown) {
this.success = success;
constructor(success: ResponseStatus, message: string, responseObject: T, statusCode: number) {
this.success = _.isEqual(success, ResponseStatus.Success);
this.message = message;
this.responseObject = responseObject;
this.errors = errors;
this.statusCode = statusCode;
}
}
6 changes: 6 additions & 0 deletions src/common/utils/commonValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { z } from 'zod';

export const commonValidations = {
id: z.string().regex(/^\d+$/, 'ID must be a number').transform(Number),
// ... other common validations
};
2 changes: 1 addition & 1 deletion src/common/utils/envConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export const getPort = () => getEnvVar<number>('PORT', 'number');
export const getNodeEnv = () => getEnvVar<string>('NODE_ENV', 'string');
export const getCorsOrigin = () => getEnvVar<string>('CORS_ORIGIN', 'string');

function getEnvVar<T extends string | number>(key: string, type: 'string' | 'number'): T {
export function getEnvVar<T extends string | number>(key: string, type: 'string' | 'number'): T {
const value = process.env[key];
if (value == null) {
throw new Error(`Unknown process.env.${key}: ${value}. Is your .env file setup?`);
Expand Down
20 changes: 20 additions & 0 deletions src/common/utils/httpHandlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { NextFunction, Request, Response } from 'express';
import { StatusCodes } from 'http-status-codes';
import { ZodError, ZodSchema } from 'zod';

import { ResponseStatus, ServiceResponse } from '@common/models/serviceResponse';

export const handleServiceResponse = (serviceResponse: ServiceResponse<any>, response: Response) => {
return response.status(serviceResponse.statusCode).send(serviceResponse);
};

export const validateRequest = (schema: ZodSchema) => (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse({ body: req.body, query: req.query, params: req.params });
next();
} catch (err) {
const errorMessage = `Invalid input: ${(err as ZodError).errors.map((e) => e.message).join(' ')}`;
const statusCode = StatusCodes.BAD_REQUEST;
res.status(statusCode).send(new ServiceResponse<null>(ResponseStatus.Failed, errorMessage, null, statusCode));
}
};
Loading

0 comments on commit 71e7be2

Please sign in to comment.