├── .gitignore ├── src ├── enums.ts ├── types.d.ts ├── index.ts ├── routes │ └── diaries.ts ├── services │ ├── diaries.json │ └── diaryServices.ts └── utils.ts ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build -------------------------------------------------------------------------------- /src/enums.ts: -------------------------------------------------------------------------------- 1 | export enum Weather { 2 | Sunny = 'sunny', 3 | Rainy = 'rainy', 4 | Cloudy = 'cloudy', 5 | Windy = 'windy', 6 | Stormy = 'stormy' 7 | } 8 | 9 | export enum Visibility { 10 | Great = 'great', 11 | Good = 'good', 12 | Ok = 'ok', 13 | Poor = 'poor' 14 | } 15 | -------------------------------------------------------------------------------- /src/types.d.ts: -------------------------------------------------------------------------------- 1 | import { Weather, Visibility } from './enums' 2 | 3 | export interface DiaryEntry { 4 | id: number 5 | date: string 6 | weather: Weather 7 | visibility: Visibility 8 | comment: string 9 | } 10 | 11 | export type NonSensitiveInfoDiaryEntry = Omit 12 | export type NewDiaryEntry = Omit 13 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' // ESModules 2 | // const express = require('express') -> commonjs 3 | 4 | import diaryRouter from './routes/diaries' 5 | 6 | const app = express() 7 | app.use(express.json()) // middleware que transforma la req.body a un json 8 | 9 | const PORT = 3000 10 | 11 | app.get('/ping', (_req, res) => { 12 | console.log('someone pinged here!!') 13 | res.send('pong') 14 | }) 15 | 16 | app.use('/api/diaries', diaryRouter) 17 | 18 | app.listen(PORT, () => { 19 | console.log(`Server running on port ${PORT}`) 20 | }) 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-typescript", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "ts-node-dev src/index.ts", 8 | "lint": "ts-standard", 9 | "start": "node build/index.js", 10 | "tsc": "tsc", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "devDependencies": { 17 | "@types/express": "4.17.12", 18 | "ts-node-dev": "^1.1.6", 19 | "ts-standard": "^10.0.0", 20 | "typescript": "4.3.2" 21 | }, 22 | "dependencies": { 23 | "express": "4.17.1" 24 | }, 25 | "eslintConfig": { 26 | "parserOptions": { 27 | "project": "./tsconfig.json" 28 | }, 29 | "extends": ["./node_modules/ts-standard/eslintrc.json"] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/routes/diaries.ts: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import * as diaryServices from '../services/diaryServices' 3 | import toNewDiaryEntry from '../utils' 4 | 5 | const router = express.Router() 6 | 7 | router.get('/', (_req, res) => { 8 | res.send(diaryServices.getEntriesWithoutSensitiveInfo()) 9 | }) 10 | 11 | router.get('/:id', (req, res) => { 12 | const diary = diaryServices.findById(+req.params.id) 13 | 14 | return (diary != null) 15 | ? res.send(diary) 16 | : res.sendStatus(404) 17 | }) 18 | 19 | router.post('/', (req, res) => { 20 | try { 21 | const newDiaryEntry = toNewDiaryEntry(req.body) 22 | 23 | const addedDiaryEntry = diaryServices.addDiary(newDiaryEntry) 24 | 25 | res.json(addedDiaryEntry) 26 | } catch (e) { 27 | res.status(400).send(e.message) 28 | } 29 | }) 30 | 31 | export default router 32 | -------------------------------------------------------------------------------- /src/services/diaries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 4, 4 | "date": "2017-05-11", 5 | "weather": "cloudy", 6 | "visibility": "good", 7 | "comment": "I almost failed the landing but I survived" 8 | }, 9 | { 10 | "id": 1, 11 | "date": "2017-01-01", 12 | "weather": "rainy", 13 | "visibility": "poor", 14 | "comment": "Pretty scary flight, I'm glad I'm alive" 15 | }, 16 | { 17 | "id": 2, 18 | "date": "2017-04-01", 19 | "weather": "sunny", 20 | "visibility": "good", 21 | "comment": "Everything went better than expected, I'm learning much" 22 | }, 23 | { 24 | "id": 3, 25 | "date": "2017-04-15", 26 | "weather": "windy", 27 | "visibility": "good", 28 | "comment": "I'm getting pretty confident although I hit a flock of birds" 29 | } 30 | ] -------------------------------------------------------------------------------- /src/services/diaryServices.ts: -------------------------------------------------------------------------------- 1 | import { DiaryEntry, NewDiaryEntry, NonSensitiveInfoDiaryEntry } from '../types' 2 | import diaryData from './diaries.json' 3 | 4 | const diaries: DiaryEntry[] = diaryData as DiaryEntry[] 5 | 6 | export const getEntries = (): DiaryEntry[] => diaries 7 | 8 | export const findById = (id: number): NonSensitiveInfoDiaryEntry | undefined => { 9 | const entry = diaries.find(d => d.id === id) 10 | if (entry != null) { 11 | const { comment, ...restOfDiary } = entry 12 | return restOfDiary 13 | } 14 | 15 | return undefined 16 | } 17 | 18 | export const getEntriesWithoutSensitiveInfo = (): NonSensitiveInfoDiaryEntry[] => { 19 | return diaries.map(({ id, date, weather, visibility }) => { 20 | return { 21 | id, 22 | date, 23 | weather, 24 | visibility 25 | } 26 | }) 27 | } 28 | 29 | export const addDiary = (newDiaryEntry: NewDiaryEntry): DiaryEntry => { 30 | const newDiary = { 31 | id: Math.max(...diaries.map(d => d.id)) + 1, 32 | ...newDiaryEntry 33 | } 34 | 35 | diaries.push(newDiary) 36 | return newDiary 37 | } 38 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { NewDiaryEntry } from './types' 2 | import { Weather, Visibility } from './enums' 3 | 4 | const parseComment = (commentFromRequest: any): string => { 5 | if (!isString(commentFromRequest)) { 6 | throw new Error('Incorrect or missing comment') 7 | } 8 | 9 | return commentFromRequest 10 | } 11 | 12 | const parseDate = (dateFromRequest: any): string => { 13 | if (!isString(dateFromRequest) || !isDate(dateFromRequest)) { 14 | throw new Error('Incorrect or missing date') 15 | } 16 | 17 | return dateFromRequest 18 | } 19 | 20 | const parseWeather = (weatherFromRequest: any): Weather => { 21 | if (!isString(weatherFromRequest) || !isWeather(weatherFromRequest)) { 22 | throw new Error('Incorrect or missing Weather') 23 | } 24 | 25 | return weatherFromRequest 26 | } 27 | 28 | const parseVisibility = (visibilityFromRequest: any): Visibility => { 29 | if (!isString(visibilityFromRequest) || !isVisibility(visibilityFromRequest)) { 30 | throw new Error('Incorrect or missing Visibility') 31 | } 32 | 33 | return visibilityFromRequest 34 | } 35 | 36 | const isWeather = (param: any): boolean => { 37 | return Object.values(Weather).includes(param) 38 | } 39 | 40 | const isString = (string: string): boolean => { 41 | return typeof string === 'string' 42 | } 43 | 44 | const isDate = (date: string): boolean => { 45 | return Boolean(Date.parse(date)) 46 | } 47 | 48 | const isVisibility = (param: any): boolean => { 49 | return Object.values(Visibility).includes(param) 50 | } 51 | 52 | const toNewDiaryEntry = (object: any): NewDiaryEntry => { 53 | const newEntry: NewDiaryEntry = { 54 | comment: parseComment(object.comment), 55 | date: parseDate(object.date), 56 | weather: parseWeather(object.weather), 57 | visibility: parseVisibility(object.visibility) 58 | } 59 | 60 | return newEntry 61 | } 62 | 63 | export default toNewDiaryEntry 64 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./build", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 43 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 44 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 45 | 46 | /* Module Resolution Options */ 47 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 48 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 49 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 50 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 51 | // "typeRoots": [], /* List of folders to include type definitions from. */ 52 | // "types": [], /* Type declaration files to be included in compilation. */ 53 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 54 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 55 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 56 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 57 | 58 | /* Source Map Options */ 59 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 60 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 61 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 62 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 63 | 64 | /* Experimental Options */ 65 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 66 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 67 | 68 | /* Advanced Options */ 69 | "resolveJsonModule": true, 70 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 71 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 72 | } 73 | } 74 | --------------------------------------------------------------------------------