Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas committed Feb 11, 2022
0 parents commit 6e66e19
Show file tree
Hide file tree
Showing 32 changed files with 7,264 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
2 changes: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
bin
30 changes: 30 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"env": {
"node": true,
"jest": true
},
"extends": ["airbnb-base", "plugin:jest/recommended", "plugin:security/recommended", "plugin:prettier/recommended"],
"plugins": ["jest", "security", "prettier"],
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"no-console": "warn",
"no-unused-vars": "warn",
"func-names": "off",
"no-underscore-dangle": "off",
"consistent-return": "off",
"jest/expect-expect": "off",
"security/detect-object-injection": "off",
"import/extensions": [
"error",
"ignorePackages",
{
"js": "never",
"jsx": "never",
"ts": "never",
"tsx": "never"
}
]
}
}
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Convert text file line endings to lf
* text eol=lf
*.js text
14 changes: 14 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Dependencies
node_modules

# yarn error logs
yarn-error.log

# Environment varibales
.env*
!.env*.example

# Code coverage
coverage

cachedToken.json
1 change: 1 addition & 0 deletions .husky/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_
3 changes: 3 additions & 0 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"*.js": "eslint"
}
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
coverage

4 changes: 4 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"printWidth": 125
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# API Template - Typescript/Express

Use yarn to install and run.
15 changes: 15 additions & 0 deletions ecosystem.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"apps": [
{
"name": "app",
"script": "src/index.js",
"instances": 1,
"autorestart": true,
"watch": false,
"time": true,
"env": {
"NODE_ENV": "production"
}
}
]
}
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
exports = {
testEnvironment: 'node',
testEnvironmentOptions: {
NODE_ENV: 'test',
},
restoreMocks: true,
coveragePathIgnorePatterns: ['node_modules', 'src/config', 'src/app.js', 'tests'],
coverageReporters: ['text', 'lcov', 'clover', 'html'],
};
58 changes: 58 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "api",
"version": "0.1.0",
"description": "",
"bin": "bin/createNodejsApp.js",
"main": "src/index.ts",
"author": "",
"private": true,
"engines": {
"node": ">=14.0.0"
},
"scripts": {
"start": "pm2 start ecosystem.config.json --no-daemon",
"dev": "cross-env NODE_ENV=development nodemon",
"test": "jest -i --colors --verbose --detectOpenHandles",
"test:ci": "jest -i --verbose --detectOpenHandles",
"test:watch": "jest -i --watchAll",
"coverage": "jest -i --coverage",
"coverage:coveralls": "jest -i --coverage --coverageReporters=text-lcov | coveralls",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prettier": "prettier --check **/*.ts",
"prettier:fix": "prettier --write **/*.ts",
"prepare": "husky install"
},
"dependencies": {
"@types/node": "^17.0.17",
"compression": "^1.7.4",
"cors": "^2.8.5",
"cross-env": "^7.0.0",
"dotenv": "^8.2.0",
"express": "^4.17.1",
"helmet": "^4.1.0",
"http-status": "^1.4.0",
"joi": "^17.3.0",
"mongoose": "^6.1.5",
"pm2": "^4.1.2",
"typescript": "^4.5.5",
"validator": "^13.0.0",
"winston": "^3.2.1",
"xss-clean": "^0.1.1"
},
"devDependencies": {
"coveralls": "^3.0.7",
"eslint": "^7.0.0",
"eslint-config-airbnb-base": "^14.0.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^24.0.1",
"eslint-plugin-prettier": "^3.1.1",
"eslint-plugin-security": "^1.4.0",
"husky": "^5.1.2",
"jest": "26.6.0",
"lint-staged": "^10.0.7",
"nodemon": "^2.0.0",
"prettier": "^2.0.5"
}
}
44 changes: 44 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const cors = require('cors');
const express = require('express');
const helmet = require('helmet');
const xss = require('xss-clean');
const compression = require('compression');
const httpStatus = require('http-status');
const ApiError = require('./utils/apiError');

const app = express();

const { errorConverter, errorHandler } = require('./middlewares/error');
const routes = require('./routes');

// set security HTTP headers
app.use(helmet());

app.use(cors({ origin: '*' }));

app.options('*', cors());

// parse json request body
app.use(express.json());

// sanitize request data
app.use(xss());

// gzip compression
app.use(compression());

// api routes
app.use('/api', routes);

// send back a 404 error for any unknown api request
app.use(cors(), (req, res, next) => {
next(new ApiError(httpStatus.NOT_FOUND, 'Not found'));
});

// convert error to ApiError, if needed
app.use(errorConverter);

// handle error
app.use(errorHandler);

export = app;
37 changes: 37 additions & 0 deletions src/config/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const dotenv = require('dotenv');
const path = require('path');
const Joi = require('joi');

dotenv.config({
path: path.join(__dirname, '../../.env'),
});

const envVarsSchema = Joi.object()
.keys({
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
PORT: Joi.number().default(3000),
//MONGODB_URL: Joi.string().required().description('Mongo DB url'),
})
.unknown();

const { value: envVars, error } = envVarsSchema
.prefs({
errors: {
label: 'key',
},
})
.validate(process.env);

if (error) {
throw new Error(`Config validation error: ${error.message}`);
}

export = {
env: envVars.NODE_ENV,
port: envVars.PORT,
mongoose: {
url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''),
options: {
},
}
};
26 changes: 26 additions & 0 deletions src/config/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const winston = require('winston');
const config = require('./config');

const enumerateErrorFormat = winston.format((info) => {
if (info instanceof Error) {
Object.assign(info, { message: info.stack });
}
return info;
});

const logger = winston.createLogger({
level: config.env === 'development' ? 'debug' : 'info',
format: winston.format.combine(
enumerateErrorFormat(),
config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
winston.format.splat(),
winston.format.printf(({ level, message }) => `${level}: ${message}`)
),
transports: [
new winston.transports.Console({
stderrLevels: ['error'],
}),
],
});

export = logger;
9 changes: 9 additions & 0 deletions src/controllers/example.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const catchAsync = require('../utils/catchAsync');
import { exampleService } from '../services';

const exampleController = catchAsync(async (req, res) => {
const result = exampleService.example();
res.send(result);
});

export = exampleController;
5 changes: 5 additions & 0 deletions src/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import exampleController from './example.controller';

export {
exampleController,
};
33 changes: 33 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const app = require('./app.ts');
const config = require('./config/config');
const logger = require('./config/logger');

let server = app.listen(config.port, () => {
logger.info(`Listening to port ${config.port}`);
});

const exitHandler = () => {
if (server) {
server.close(() => {
logger.info('Server closed');
process.exit(1);
});
} else {
process.exit(1);
}
};

const unexpectedErrorHandler = (error: any) => {
logger.error(error);
exitHandler();
};

process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);

process.on('SIGTERM', () => {
logger.info('SIGTERM received');
if (server) {
server.close();
}
});
47 changes: 47 additions & 0 deletions src/middlewares/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const httpStatus = require('http-status');
const config = require('../config/config');
const logger = require('../config/logger');
// eslint-disable-next-line import/no-unresolved
const ApiError = require('../utils/apiError');

const errorConverter = (err, req, res, next) => {
let error = err;
if (err.name === 'UnauthorizedError') {
error = new ApiError(401, 'Invalid Token', false, err.stack);
}

if (!(error instanceof ApiError)) {
const statusCode = error.statusCode ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR;
const message = error.message || httpStatus[statusCode];
error = new ApiError(statusCode, message, false, err.stack);
}
next(error);
};

// eslint-disable-next-line no-unused-vars
const errorHandler = (err, req, res, next) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
}

res.locals.errorMessage = err.message;

const response = {
code: statusCode,
message,
...(config.env === 'development' && { stack: err.stack }),
};

if (config.env === 'development') {
logger.error(err);
}

res.status(statusCode).send(response);
};

export = {
errorConverter,
errorHandler,
};
2 changes: 2 additions & 0 deletions src/models/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export = {toJSON: require('./toJSON.plugin')};
// module.exports.paginate = require('./paginate.plugin');
Loading

0 comments on commit 6e66e19

Please sign in to comment.