git init
yarn init -y
# tsoa, hapi
yarn add tsoa @hapi/hapi
# typescript
yarn add -D typescript @types/node @types/hapi__hapi
yarn run tsc --init --target es2021 --experimentalDecorators --outDir ./dist
# eslint
yarn add -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
yarn run eslint --init
# prettier
yarn add -D prettier eslint-config-prettier eslint-plugin-prettier
tsconfig.eslint.json
{
"compilerOptions": {
"types": [
"@types/node"
],
"noEmit": true,
"allowJs": true,
},
"extends": "./tsconfig.json",
"include": [
".eslintrc.js",
"src/**/*.ts",
"test/**/*.ts",
]
}
.eslintrc.js
module.exports = {
root: true,
env: {
es6: true,
node: true,
},
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'prettier'],
parserOptions: {
project: ['./tsconfig.eslint.json'],
sourceType: 'module',
tsConfigRootDir: __dirname,
warnOnUnsupportedTypeScriptVersion: false,
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'prettier',
],
rules: {
'require-await': 'off',
'@typescript-eslint/require-await': 'off',
'prettier/prettier': 2,
},
}
.eslintignore
build
node_modules
.prettierrc
{
"semi": false,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 80
}
.prettierignore
src/routes.ts
package.json
{
"scripts": {
"build": "tsc",
"fmt": "prettier --config .prettierrc '{src,test}/**/*.ts' --write --no-color",
"gen": "tsoa spec-and-routes",
"lint": "eslint '{src,test}/**/*.ts'",
"start": "node dist/server.js"
}
}
- create
src/users/users.ts
exporting theUser
interface - create
src/users/users-service.ts
exporting theUsersCreationParams
type andUsersService
class - create
src/users/users-controller.ts
exporting theUsersController
class
tsoa.json
{
"entryFile": "src/app.ts",
"noImplicitAdditionalProperties": "throw-on-extras",
"controllerPathGlobs": ["src/**/*-controller.ts"],
"spec": {
"outputDirectory": "dist",
"specVersion": 3
},
"routes": {
"middleware": "hapi",
"routesDir": "src"
}
}
src/app.ts
import * as Hapi from '@hapi/hapi'
import { RegisterRoutes } from './routes'
export const app = (host = 'localhost', port = 3000): Hapi.Server => {
const server = Hapi.server({ host, port })
RegisterRoutes(server)
return server
}
src/server.ts
import { app } from './app'
const host = process.env.HOST || 'localhost'
const port = Number(process.env.PORT) || 3000
void app(host, port).start()
console.log(`Server running on ${host}:${port}`)
yarn gen
yarn build
yarn start
# GET /users/{id}
curl -i http://localhost:3000/users/1 \
-H 'Accept: application/json'
# POST /users and see headers
curl -i -X POST http://localhost:3000/users \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","name":"John Doe","phoneNumbers":[]}'
# POST /users and see success response
curl -s -X POST http://localhost:3000/users \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"email":"[email protected]","name":"John Doe","phoneNumbers":[]}' | jq
# POST /users and see error response
curl -s -X POST http://localhost:3000/users \
-H 'Accept: application/json' \
-H 'Content-Type: application/json' \
-d '{"id":123,"name":"Foo Bar","status":"Happy"}' | jq
yarn add -D nodemon ts-node concurrently
nodemon.json
{
"exec": "ts-node src/server.ts",
"watch": ["src"],
"ignore": ["src/routes.ts"],
"ext": "ts"
}
package.json
{
"scripts": {
"dev": "concurrently \"nodemon\" \"nodemon -x tsoa spec-and-routes\"",
}
}
yarn dev
yarn add -D redoc-cli
package.json
{
"scripts": {
"bundle:api-docs": "redoc-cli bundle dist/swagger.json -o dist/api-docs.html",
"serve:api-docs": "redoc-cli serve dist/swagger.json",
}
}
yarn bundle:api-docs
yarn serve:api-docs