Darío Blanco Iturriaga
CTO @ MindDoc
April 11, 2019
Front-End and Back-End tech stack should be as small as possible
var Rectangle = function (id, x, y, width, height) {
Shape.call(this, id, x, y);
this.width = width;
this.height = height;
};
Rectangle.prototype = Object.create(Shape.prototype);
Rectangle.prototype.constructor = Rectangle;
var Circle = function (id, x, y, radius) {
Shape.call(this, id, x, y);
this.radius = radius;
};
Circle.prototype = Object.create(Shape.prototype);
Circle.prototype.constructor = Circle;class Rectangle extends Shape {
constructor (id, x, y, width, height) {
super(id, x, y)
this.width = width
this.height = height
}
}
class Circle extends Shape {
constructor (id, x, y, radius) {
super(id, x, y)
this.radius = radius
}
}Oh wait... JavaScript is not statically-typed!
Let's use TypeScript everywhere
Same test framework is a great productivity win
Project manifest and dependencies
TypeScript root files and compiler options
TypeScript lint rules
TypeScript source files
TypeScript Jest test specs
Jest test configuration
{
"name": "typescript-node-starter",
"version": "1.0.0",
"description": "A TypeScript Node skeleton project",
"main": "src/server.ts",
"scripts": {
"compile": "tsc",
"lint": "tslint --project .",
"test": "jest --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/minddocdev/typescript-node-starter.git"
},
"keywords": [
"typescript",
"node",
"skeleton",
"starter"
],
"author": "development@minddoc.com",
"license": "ISC",
"bugs": {
"url": "https://github.com/minddocdev/typescript-node-starter/issues"
},
"homepage": "https://github.com/minddocdev/typescript-node-starter#readme",
"devDependencies": {
"@types/express": "^4.16.1",
"@types/jest": "^24.0.11",
"@types/node": "^11.13.4",
"jest": "^24.7.1",
"ts-jest": "^24.0.2",
"tslint": "^5.15.0",
"tslint-config-airbnb": "^5.11.1"
},
"dependencies": {
"express": "^4.16.4",
"typescript": "^3.4.3"
}
}
{
"compilerOptions": {
"baseUrl": ".",
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"dom",
"es2017"
],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": [
"src/*"
],
"#/*": [
"test/*"
]
}
},
"exclude": [
"node_modules",
],
"include": [
"src/**/*.ts",
"test/**/*.ts",
]
}
tsc
{
"defaultSeverity": "error",
"extends": [
"tslint-config-airbnb"
],
"jsRules": {},
"rules": {
"import-name": false,
"no-duplicate-variable": true
},
"rulesDirectory": []
}
tslint --project .
const path = require('path');
module.exports = {
collectCoverageFrom: [
'src/**/*.ts',
'!src/server.ts',
],
coverageDirectory: '<rootDir>/test/coverage',
coverageReporters: process.env.CI ? ['text'] : ['json', 'lcov', 'text', 'clover'],
coverageThreshold: {
global: {
branches: 100,
functions: 100,
lines: 100,
},
},
globals: {
'ts-jest': {
tsConfig: 'tsconfig.json',
}
},
moduleFileExtensions: [
'json',
'ts',
'js',
],
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
'^#/(.*)$': '<rootDir>/test/$1',
},
rootDir: path.resolve(__dirname),
testEnvironment: 'node',
testMatch: [
'**/test/**/*.spec.(ts|js)',
],
transform: {
'^.+\\.(ts|tsx)$': 'ts-jest',
},
};
jest --coverage
{
"compilerOptions": {
"baseUrl": ".",
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"dom",
"es2017"
],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": [
"src/*"
],
"#/*": [
"test/*"
]
}
},
"files": [
"node_modules/@types/node/index.d.ts",
"node_modules/@types/jest/index.d.ts",
"node_modules/@types/express/index.d.ts"
],
"exclude": [
"node_modules",
],
"include": [
"src/**/*.ts",
"test/**/*.ts",
]
}
{
"name": "typescript-node-starter",
"version": "1.0.0",
"description": "A TypeScript Node skeleton project",
"main": "src/server.ts",
"scripts": {
"compile": "tsc",
"dev": "ts-node-dev -r tsconfig-paths/register --ignore-watch node_modules src/server.ts",
"lint": "tslint --project .",
"start": "ts-node --transpile-only -r tsconfig-paths/register src/server.ts",
"test": "jest --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/minddocdev/typescript-node-starter.git"
},
"keywords": [
"typescript",
"node",
"skeleton",
"starter"
],
"author": "development@minddoc.com",
"license": "ISC",
"bugs": {
"url": "https://github.com/minddocdev/typescript-node-starter/issues"
},
"homepage": "https://github.com/minddocdev/typescript-node-starter#readme",
"devDependencies": {
"@types/express": "^4.16.1",
"@types/jest": "^24.0.11",
"@types/node": "^11.13.4",
"jest": "^24.7.1",
"ts-jest": "^24.0.2",
"ts-node-dev": "^1.0.0-pre.32",
"tslint": "^5.15.0",
"tslint-config-airbnb": "^5.11.1"
},
"dependencies": {
"express": "^4.16.4",
"ts-node": "^8.0.3",
"tsconfig-paths": "^3.8.0",
"typescript": "^3.4.3"
}
}
ts-node-dev -r tsconfig-paths/register --ignore-watch node_modules src/server.ts
# ------------------------------------------------------
# Dockerfile
# ------------------------------------------------------
# image: typescript-node-starter
# tag: latest
# name: minddocdev/typescript-node-starter
# repo:
# how-to: docker build -t minddocdev/typescript-node-starter:latest .
# Requires: node:alpine
# authors: development@minddoc.com
# ------------------------------------------------------
FROM node:11.13-alpine
LABEL maintainer="development@minddoc.com"
# Create app directory and install production dependencies
WORKDIR /usr/src/app
COPY tsconfig.json package.json package-lock.json ./
RUN npm install --only=production
# Copy src files (Use .dockerignore to exclude non essential)
COPY src/ ./src
# Set permissions for the node user
RUN chown -R node:node .
USER node
# Run ts-node with src/server.ts entry point
CMD ["npm", "start"]
Dockerfile
{
"name": "typescript-node-starter",
"version": "1.0.0",
"description": "A TypeScript Node skeleton project",
"main": "src/server.ts",
"scripts": {
"compile": "tsc",
"dev": "ts-node-dev -r tsconfig-paths/register --ignore-watch node_modules src/server.ts",
"docker": "docker build -t minddocdev/typescript-node-starter:latest . && docker run -it minddocdev/typescript-node-starter:latest",
"lint": "tslint --project .",
"start": "ts-node --transpile-only -r tsconfig-paths/register src/server.ts",
"test": "jest --coverage"
},
"repository": {
"type": "git",
"url": "git+https://github.com/minddocdev/typescript-node-starter.git"
},
"keywords": [
"typescript",
"node",
"skeleton",
"starter"
],
"author": "development@minddoc.com",
"license": "ISC",
"bugs": {
"url": "https://github.com/minddocdev/typescript-node-starter/issues"
},
"homepage": "https://github.com/minddocdev/typescript-node-starter#readme",
"devDependencies": {
"@types/express": "^4.16.1",
"@types/jest": "^24.0.11",
"@types/node": "^11.13.4",
"jest": "^24.7.1",
"ts-jest": "^24.0.2",
"ts-node-dev": "^1.0.0-pre.32",
"tslint": "^5.15.0",
"tslint-config-airbnb": "^5.11.1"
},
"dependencies": {
"express": "^4.16.4",
"ts-node": "^8.0.3",
"tsconfig-paths": "^3.8.0",
"typescript": "^3.4.3"
}
}
Production app!
{
"compilerOptions": {
"baseUrl": ".",
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"sourceMap": true,
"strict": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"dom",
"es2017"
],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": [
"src/*"
],
"#/*": [
"test/*"
]
},
"typeRoots": [
"types"
],
"types": [
"dependencies",
"mytypes"
]
},
"files": [
"node_modules/@types/node/index.d.ts",
"node_modules/@types/jest/index.d.ts",
"node_modules/@types/express/index.d.ts"
],
"exclude": [
"node_modules",
],
"include": [
"src/**/*.ts",
"test/**/*.ts",
"types/**/*.d.ts"
]
}
Only packages under ./types are global
Only specific packages from ./types will be global (optional)
Set specific node_modules types as global
import * as express from 'express';
export const expressApp = express();
/* istanbul ignore next */
expressApp.get('/', (req, res) => {
const response: HelloWorldResponse = { message: 'Hello World!' };
res.send(response);
});
/**
* Start the express application.
*
* @param port The port to listen to.
*/
export function start(port = 3000) {
expressApp.listen(port, (err: any) => {
if (err) {
console.error(`Unable to start app. Found error: ${err.message}`);
return;
}
console.info(`Example app listening on port ${port}!`);
});
}
// Type package for own global type definitions
interface HelloWorldResponse {
message: string;
}
src/app.ts
types/mytypes/index.d.ts
No need to import HelloWorldResponse
tsc will complain
Scaling into Back-End microservices makes this worse
More repository overhead
@minddoc
@darioblanco
@minddocdev
@darioblanco
https://stackoverflow.com/jobs/companies/minddoc-by-schoen-clinic
https://github.com/minddocdev/typescript-node-starter