Node-Typescript-Setup
April 29, 2021
This is my prefered setup when I start a new Node project, especially when it covers TypeScript on it’s stack. The setup covers code-convention checks as well as docker builds and a deployment pipeline.
Step 1: Git
initialise repository: git init
create .gitignore
and import content from node-typescript-starter
Step 2: Node workspace and dependencies
initialise Node project: npm init -y
install following dependencies:
npm install prettier --save-dev
npm install typescript --save-dev
npm install eslint --save-dev
npm install @typescript-eslint/eslint-plugin --save-dev
npm install @typescript-eslint/parser --save-dev
Step 3: Configure Prettier
Create .prettierrc
Use the following configuration:
{
"singleQuote": true,
"semi": true,
"trailingComma": "none",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 90
}
Step 4: Configure EsLint
Create .eslintrc.json
Use the following eslint-configuration
{
"env": {
"browser": true,
"es2021": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": ["@typescript-eslint"],
"rules": {
"quotes": ["error", "double"],
"sort-imports": [
"error",
{
"ignoreCase": false,
"ignoreDeclarationSort": false,
"ignoreMemberSort": false,
"memberSyntaxSortOrder": ["none", "all", "multiple", "single"],
"allowSeparatedGroups": false
}
],
"comma-dangle": ["error", "never"],
"semi": ["error", "always"],
"@typescript-eslint/array-type": 2,
"@typescript-eslint/member-ordering": 2,
"@typescript-eslint/no-unnecessary-boolean-literal-compare": 2,
"@typescript-eslint/no-unnecessary-condition": 2,
"@typescript-eslint/prefer-for-of": 2,
"@typescript-eslint/require-array-sort-compare": 2,
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/no-explicit-any": 1,
"@typescript-eslint/no-inferrable-types": [
"warn", {
"ignoreParameters": true
}
],
"@typescript-eslint/no-unused-vars": "warn"
"@typescript-eslint/naming-convention": [
"error",
{
"selector": "typeLike",
"format": ["PascalCase"]
},
{
"selector": "variable",
"types": ["boolean"],
"format": ["camelCase"],
"prefix": ["is"]
}
]
}
}
Step 5: Configure TypeScript
Create initial tsconfig by running tsc --init
. Doublecheck the content against the following.
{
"compilerOptions": {
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
"lib": ["dom", "es6"] /* Specify library files to be included in the compilation. */,
"outDir": "dist" /* Redirect output structure to the directory. */,
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
"removeComments": true /* Do not emit comments to output. */,
"strict": true /* Enable all strict type-checking options. */,
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */,
"strictNullChecks": true /* Enable strict null checks. */,
"strictPropertyInitialization": true /* Enable strict checking of property initialization in classes. */,
"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */,
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true /* Skip type checking of declaration files. */,
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
},
"compileOnSave": true
}
Step 6: Setup lint-staged
First install lint-staged via npx mrm lint-staged
. Adjust package.json
to use lint-staged as intended. The following snippet is an excerpt of an example package.json
.
{
...
"scripts": {
"prettify": "prettier --write",
"lint": "eslint --cache --fix"
},
...
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.ts": [
"npm run prettify",
"npm run lint"
]
}
}
Step 7: Setup Docker
To build your application with Docker create a Dockerfile
at the root and implement the wanted behaviour. This could look like the following example.
FROM node:latest
ENV API_KEY=
WORKDIR /usr/<project>
COPY package.json /usr/<project>/
RUN npm install
COPY ./ /usr/<project>
RUN npm run build
COPY ./ /usr/<project>
CMD ["sh", "-c", "NODE_ENV=production node dist/index.js $API_KEY"]
Step 8: Setup Gitlab CI
Create .gitlab-ci.yml
and use the outlined content to run a 5-stage deployment pipeline on every MergeRequest or commit to Master
include:
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
stages:
- prettier
- lint
- typecheck
- test
- deploy
prettier:
stage: prettier
image: node:latest
script:
- 'npm ci'
- 'npm run prettiercheck'
lint:
stage: lint
image: node:latest
script:
- 'npm ci'
- 'npm run lint:all'
typecheck:
stage: typecheck
image: node:latest
script:
- 'npm ci'
- 'npm run typecheck'
test:
stage: test
image: node:latest
script:
- 'npm ci'
- 'npm run test'
deploy:
stage: deploy
before_script:
- 'docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY'
image: 'docker:latest'
script:
- 'docker image build --tag registry.gitlab.com/<username>/<project>:latest .'
- 'docker push registry.gitlab.com/<username>/<project>:latest'
services:
- 'docker:dind'