├── .env.sample ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── api │ ├── emojis.ts │ └── index.ts ├── app.ts ├── index.ts ├── interfaces │ ├── ErrorResponse.ts │ └── MessageResponse.ts └── middlewares.ts ├── test ├── api.test.ts └── app.test.ts └── tsconfig.json /.env.sample: -------------------------------------------------------------------------------- 1 | NODE_ENV=development -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "jest": true 5 | }, 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "project": [ 9 | "./tsconfig.json" 10 | ] 11 | }, 12 | "extends": "airbnb-typescript/base", 13 | "plugins": [ 14 | "import", 15 | "@typescript-eslint" 16 | ], 17 | "rules": { 18 | "comma-dangle": 0, 19 | "no-underscore-dangle": 0, 20 | "no-param-reassign": 0, 21 | "no-return-assign": 0, 22 | "camelcase": 0, 23 | "import/extensions": 0, 24 | "@typescript-eslint/no-redeclare": 0 25 | }, 26 | "settings": { 27 | "import/parsers": { 28 | "@typescript-eslint/parser": [ 29 | ".ts", 30 | ".tsx" 31 | ] 32 | }, 33 | "import/resolver": { 34 | "typescript": {} 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # parcel-bundler cache (https://parceljs.org/) 61 | .cache 62 | 63 | # next.js build output 64 | .next 65 | 66 | # nuxt.js build output 67 | .nuxt 68 | 69 | # vuepress build output 70 | .vuepress/dist 71 | 72 | # Serverless directories 73 | .serverless 74 | 75 | dist -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2020 CJ R. 2 | 3 | Permission is hereby granted, free 4 | of charge, to any person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, copy, modify, merge, 7 | publish, distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to the 9 | following conditions: 10 | 11 | The above copyright notice and this permission notice 12 | (including the next paragraph) shall be included in all copies or substantial 13 | portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO 18 | EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 19 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 20 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Express API Starter with Typescript 2 | 3 | How to use this template: 4 | 5 | ```sh 6 | npx create-express-api --typescript --directory my-api-name 7 | ``` 8 | 9 | Includes API Server utilities: 10 | 11 | * [morgan](https://www.npmjs.com/package/morgan) 12 | * HTTP request logger middleware for node.js 13 | * [helmet](https://www.npmjs.com/package/helmet) 14 | * Helmet helps you secure your Express apps by setting various HTTP headers. It's not a silver bullet, but it can help! 15 | * [dotenv](https://www.npmjs.com/package/dotenv) 16 | * Dotenv is a zero-dependency module that loads environment variables from a `.env` file into `process.env` 17 | * [cors](https://www.npmjs.com/package/cors) 18 | * CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options. 19 | 20 | Development utilities: 21 | 22 | * [typescript](https://www.npmjs.com/package/typescript) 23 | * TypeScript is a language for application-scale JavaScript. 24 | * [ts-node](https://www.npmjs.com/package/ts-node) 25 | * TypeScript execution and REPL for node.js, with source map and native ESM support. 26 | * [nodemon](https://www.npmjs.com/package/nodemon) 27 | * nodemon is a tool that helps develop node.js based applications by automatically restarting the node application when file changes in the directory are detected. 28 | * [eslint](https://www.npmjs.com/package/eslint) 29 | * ESLint is a tool for identifying and reporting on patterns found in ECMAScript/JavaScript code. 30 | * [typescript-eslint](https://typescript-eslint.io/) 31 | * Tooling which enables ESLint to support TypeScript. 32 | * [jest](https://www.npmjs.com/package/jest) 33 | * Jest is a delightful JavaScript Testing Framework with a focus on simplicity. 34 | * [supertest](https://www.npmjs.com/package/supertest) 35 | * HTTP assertions made easy via superagent. 36 | 37 | ## Setup 38 | 39 | ``` 40 | npm install 41 | ``` 42 | 43 | ## Lint 44 | 45 | ``` 46 | npm run lint 47 | ``` 48 | 49 | ## Test 50 | 51 | ``` 52 | npm run test 53 | ``` 54 | 55 | ## Development 56 | 57 | ``` 58 | npm run dev 59 | ``` 60 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ 2 | module.exports = { 3 | preset: 'ts-jest', 4 | testEnvironment: 'node', 5 | modulePathIgnorePatterns: ['/dist/'], 6 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-api-starter-ts", 3 | "version": "1.2.0", 4 | "description": " A basic starter for an express.js API with Typescript", 5 | "main": "src/index.ts", 6 | "scripts": { 7 | "start": "ts-node src/index.ts", 8 | "dev": "nodemon src/index.ts", 9 | "build": "tsc", 10 | "start:dist": "node dist/src/index.js", 11 | "lint": "eslint --fix src test", 12 | "test": "jest", 13 | "typecheck": "tsc --noEmit" 14 | }, 15 | "keywords": [], 16 | "author": "CJ R. (https://w3cj.now.sh)", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/w3cj/express-api-starter.git" 20 | }, 21 | "license": "MIT", 22 | "dependencies": { 23 | "cors": "^2.8.5", 24 | "dotenv": "^16.4.5", 25 | "express": "^4.21.1", 26 | "helmet": "^8.0.0", 27 | "morgan": "^1.10.0" 28 | }, 29 | "devDependencies": { 30 | "@types/cors": "^2.8.17", 31 | "@types/express": "^4.17.20", 32 | "@types/jest": "^29.5.13", 33 | "@types/morgan": "^1.9.9", 34 | "@types/node": "^22.7.5", 35 | "@types/supertest": "^6.0.2", 36 | "@typescript-eslint/eslint-plugin": "^7.16.1", 37 | "@typescript-eslint/parser": "^7.16.1", 38 | "eslint": "^8.57.0", 39 | "eslint-config-airbnb-typescript": "^18.0.0", 40 | "eslint-import-resolver-typescript": "^3.6.3", 41 | "eslint-plugin-import": "^2.31.0", 42 | "jest": "^29.7.0", 43 | "nodemon": "^3.1.7", 44 | "supertest": "^7.0.0", 45 | "ts-jest": "^29.2.5", 46 | "ts-node": "^10.9.2", 47 | "typescript": "^5.6.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/api/emojis.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | const router = express.Router(); 4 | 5 | type EmojiResponse = string[]; 6 | 7 | router.get<{}, EmojiResponse>('/', (req, res) => { 8 | res.json(['😀', '😳', '🙄']); 9 | }); 10 | 11 | export default router; 12 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | import MessageResponse from '../interfaces/MessageResponse'; 4 | import emojis from './emojis'; 5 | 6 | const router = express.Router(); 7 | 8 | router.get<{}, MessageResponse>('/', (req, res) => { 9 | res.json({ 10 | message: 'API - 👋🌎🌍🌏', 11 | }); 12 | }); 13 | 14 | router.use('/emojis', emojis); 15 | 16 | export default router; 17 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import morgan from 'morgan'; 3 | import helmet from 'helmet'; 4 | import cors from 'cors'; 5 | 6 | import * as middlewares from './middlewares'; 7 | import api from './api'; 8 | import MessageResponse from './interfaces/MessageResponse'; 9 | 10 | require('dotenv').config(); 11 | 12 | const app = express(); 13 | 14 | app.use(morgan('dev')); 15 | app.use(helmet()); 16 | app.use(cors()); 17 | app.use(express.json()); 18 | 19 | app.get<{}, MessageResponse>('/', (req, res) => { 20 | res.json({ 21 | message: '🦄🌈✨👋🌎🌍🌏✨🌈🦄', 22 | }); 23 | }); 24 | 25 | app.use('/api/v1', api); 26 | 27 | app.use(middlewares.notFound); 28 | app.use(middlewares.errorHandler); 29 | 30 | export default app; 31 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import app from './app'; 2 | 3 | const port = process.env.PORT || 5000; 4 | app.listen(port, () => { 5 | /* eslint-disable no-console */ 6 | console.log(`Listening: http://localhost:${port}`); 7 | /* eslint-enable no-console */ 8 | }); 9 | -------------------------------------------------------------------------------- /src/interfaces/ErrorResponse.ts: -------------------------------------------------------------------------------- 1 | import MessageResponse from './MessageResponse'; 2 | 3 | export default interface ErrorResponse extends MessageResponse { 4 | stack?: string; 5 | } -------------------------------------------------------------------------------- /src/interfaces/MessageResponse.ts: -------------------------------------------------------------------------------- 1 | export default interface MessageResponse { 2 | message: string; 3 | } 4 | -------------------------------------------------------------------------------- /src/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from 'express'; 2 | 3 | import ErrorResponse from './interfaces/ErrorResponse'; 4 | 5 | export function notFound(req: Request, res: Response, next: NextFunction) { 6 | res.status(404); 7 | const error = new Error(`🔍 - Not Found - ${req.originalUrl}`); 8 | next(error); 9 | } 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 12 | export function errorHandler(err: Error, req: Request, res: Response, next: NextFunction) { 13 | const statusCode = res.statusCode !== 200 ? res.statusCode : 500; 14 | res.status(statusCode); 15 | res.json({ 16 | message: err.message, 17 | stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /test/api.test.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | 3 | import app from '../src/app'; 4 | 5 | describe('GET /api/v1', () => { 6 | it('responds with a json message', (done) => { 7 | request(app) 8 | .get('/api/v1') 9 | .set('Accept', 'application/json') 10 | .expect('Content-Type', /json/) 11 | .expect(200, { 12 | message: 'API - 👋🌎🌍🌏', 13 | }, done); 14 | }); 15 | }); 16 | 17 | describe('GET /api/v1/emojis', () => { 18 | it('responds with a json message', (done) => { 19 | request(app) 20 | .get('/api/v1/emojis') 21 | .set('Accept', 'application/json') 22 | .expect('Content-Type', /json/) 23 | .expect(200, ['😀', '😳', '🙄'], done); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/app.test.ts: -------------------------------------------------------------------------------- 1 | import request from 'supertest'; 2 | 3 | import app from '../src/app'; 4 | 5 | describe('app', () => { 6 | it('responds with a not found message', (done) => { 7 | request(app) 8 | .get('/what-is-this-even') 9 | .set('Accept', 'application/json') 10 | .expect('Content-Type', /json/) 11 | .expect(404, done); 12 | }); 13 | }); 14 | 15 | describe('GET /', () => { 16 | it('responds with a json message', (done) => { 17 | request(app) 18 | .get('/') 19 | .set('Accept', 'application/json') 20 | .expect('Content-Type', /json/) 21 | .expect(200, { 22 | message: '🦄🌈✨👋🌎🌍🌏✨🌈🦄', 23 | }, done); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "sourceMap": true, 5 | "target": "esnext", 6 | "module": "commonjs", 7 | "esModuleInterop": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noImplicitAny": true, 10 | "strict": true, 11 | "skipLibCheck": true 12 | }, 13 | "include": [ 14 | "./*.js", 15 | "src/**/*.ts", 16 | "test/**/*.ts", 17 | ], 18 | } 19 | --------------------------------------------------------------------------------