├── .gitignore ├── .travis.yml ├── sequelize-express ├── src │ ├── models │ │ ├── interfaces │ │ │ └── product-interface.ts │ │ ├── product-model.ts │ │ └── index.ts │ ├── utils │ │ └── logger.ts │ ├── routers │ │ └── product-router.ts │ ├── server.ts │ └── services │ │ └── product-service.ts └── test │ └── services │ └── product-service.ts ├── configs ├── server-config.ts ├── database-config.ts ├── configs.ts └── logging-config.ts ├── tslint.json ├── custom-typings └── continuation-local-storage │ └── continuation-local-storage.d.ts ├── LICENSE ├── typings.json ├── package.json ├── gulpfile.js ├── README.md └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | build 3 | logs 4 | node_modules 5 | typings 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "stable" 4 | - "4" 5 | services: 6 | - mysql 7 | before_script: 8 | - mysql -e "create database sequelize_typescript_examples;" 9 | -------------------------------------------------------------------------------- /sequelize-express/src/models/interfaces/product-interface.ts: -------------------------------------------------------------------------------- 1 | import {Instance} from "sequelize"; 2 | 3 | export interface ProductAttributes { 4 | name: string; 5 | description: string; 6 | } 7 | 8 | export interface ProductInstance extends Instance { 9 | dataValues: ProductAttributes; 10 | } 11 | -------------------------------------------------------------------------------- /configs/server-config.ts: -------------------------------------------------------------------------------- 1 | export interface ServerConfig { 2 | port: number; 3 | session: { 4 | secret: string, 5 | name: string, 6 | resave: boolean, 7 | saveUninitialized: boolean, 8 | proxy: boolean 9 | }; 10 | } 11 | 12 | export const serverConfig: ServerConfig = { 13 | port: 3000, 14 | session: { 15 | secret: "sequelize-typescript-examples", 16 | name: "sequelize-typescript-examples", 17 | resave: false, 18 | saveUninitialized: false, 19 | proxy: false 20 | } 21 | }; 22 | -------------------------------------------------------------------------------- /configs/database-config.ts: -------------------------------------------------------------------------------- 1 | export interface DatabaseConfig { 2 | username: string; 3 | password: string; 4 | database: string; 5 | host: string; 6 | port: number; 7 | dialect: string; 8 | logging: boolean | Function; 9 | force: boolean; 10 | timezone: string; 11 | } 12 | 13 | export const databaseConfig: DatabaseConfig = { 14 | username: "travis", 15 | password: "", 16 | database: "sequelize_typescript_examples", 17 | host: "127.0.0.1", 18 | port: 3306, 19 | dialect: "mysql", 20 | logging: true, 21 | force: true, 22 | timezone: "+00:00" 23 | }; 24 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "indent": [true, "spaces"], 6 | "max-line-length": [true, 120], 7 | "no-console": [true, "log"], 8 | "no-require-imports": true, 9 | "no-string-literal": false, 10 | "no-switch-case-fall-through": true, 11 | "no-unreachable": true, 12 | "no-unused-variable": true, 13 | "quotemark": [true, "double"], 14 | "semicolon": true, 15 | "triple-equals": [true, "allow-null-check"], 16 | "variable-name": [true, "allow-leading-underscore"], 17 | "whitespace": [true, "check-branch"] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sequelize-express/src/models/product-model.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:variable-name */ 2 | 3 | import * as SequelizeStatic from "sequelize"; 4 | import {DataTypes, Sequelize} from "sequelize"; 5 | import {ProductAttributes, ProductInstance} from "./interfaces/product-interface"; 6 | 7 | export default function(sequelize: Sequelize, dataTypes: DataTypes): 8 | SequelizeStatic.Model { 9 | let Product = sequelize.define("Product", { 10 | name: {type: dataTypes.STRING, allowNull: false, primaryKey: true}, 11 | description: {type: dataTypes.TEXT, allowNull: true} 12 | }, { 13 | indexes: [], 14 | classMethods: {}, 15 | timestamps: false 16 | }); 17 | 18 | return Product; 19 | } 20 | -------------------------------------------------------------------------------- /custom-typings/continuation-local-storage/continuation-local-storage.d.ts: -------------------------------------------------------------------------------- 1 | declare module "continuation-local-storage" { 2 | interface Namespace { 3 | set(key: any, value: any): any; 4 | get(key: any): any; 5 | createContext(): any; 6 | run(fn: Function): any; 7 | bind(fn: Function, context: any): Function; 8 | enter(context: any): void; 9 | exit(context: any): void; 10 | bindEmitter(emitter: any): void; 11 | fromException(exception: void): any; 12 | name: string; 13 | active: {[key: string]: any}; 14 | _set: Array; 15 | id: any; 16 | } 17 | 18 | export function getNamespace(name: string): Namespace; 19 | export function createNamespace(name: string): Namespace; 20 | export function destroyNamespace(name: string): void; 21 | export function reset(): void; 22 | } 23 | -------------------------------------------------------------------------------- /configs/configs.ts: -------------------------------------------------------------------------------- 1 | import {databaseConfig, DatabaseConfig} from "./database-config"; 2 | import {loggingConfig, LoggingConfig} from "./logging-config"; 3 | import {serverConfig, ServerConfig} from "./server-config"; 4 | 5 | class Configs { 6 | private _databaseConfig: DatabaseConfig; 7 | private _loggingConfig: LoggingConfig; 8 | private _serverConfig: ServerConfig; 9 | 10 | constructor() { 11 | this._databaseConfig = databaseConfig; 12 | this._loggingConfig = loggingConfig; 13 | this._serverConfig = serverConfig; 14 | } 15 | 16 | getDatabaseConfig(): DatabaseConfig { 17 | return this._databaseConfig; 18 | } 19 | 20 | getLoggingConfig(): LoggingConfig { 21 | return this._loggingConfig; 22 | } 23 | 24 | getServerConfig(): ServerConfig { 25 | return this._serverConfig; 26 | } 27 | } 28 | 29 | export const configs = new Configs(); 30 | -------------------------------------------------------------------------------- /configs/logging-config.ts: -------------------------------------------------------------------------------- 1 | export interface LoggingConfig { 2 | file: { 3 | level: string, 4 | filename: string, 5 | handleExceptions: boolean, 6 | json: boolean, 7 | maxsize: number, 8 | maxFiles: number, 9 | colorize: boolean 10 | }; 11 | console: { 12 | level: string, 13 | handleExceptions: boolean, 14 | json: boolean, 15 | colorize: boolean 16 | }; 17 | directory: string; 18 | } 19 | 20 | export const loggingConfig: LoggingConfig = { 21 | file: { 22 | level: "error", 23 | filename: "sequelize-typescript-example.log", 24 | handleExceptions: true, 25 | json: true, 26 | maxsize: 5242880, 27 | maxFiles: 100, 28 | colorize: false 29 | }, 30 | console: { 31 | level: "error", 32 | handleExceptions: true, 33 | json: false, 34 | colorize: true 35 | }, 36 | directory: __dirname 37 | }; 38 | -------------------------------------------------------------------------------- /sequelize-express/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import * as cluster from "cluster"; 2 | import * as mkdirp from "mkdirp"; 3 | import * as path from "path"; 4 | import {configs} from "../../../configs/configs"; 5 | import {transports, Logger} from "winston"; 6 | import {Request, Response} from "express"; 7 | 8 | let config = configs.getLoggingConfig(); 9 | config.file.filename = `${path.join(config.directory, "../sequelize-express/logs")}/${config.file.filename}`; 10 | 11 | if (cluster.isMaster) { 12 | mkdirp.sync(path.join(config.directory, "../sequelize-express/logs")); 13 | } 14 | 15 | export const logger = new Logger({ 16 | transports: [ 17 | new transports.File(config.file), 18 | new transports.Console(config.console) 19 | ], 20 | exitOnError: false 21 | }); 22 | 23 | export const skip = (req: Request, res: Response): boolean => { 24 | return res.statusCode >= 200; 25 | }; 26 | 27 | export const stream = { 28 | write: (message: string, encoding: string): void => { 29 | logger.info(message); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ambientDependencies": { 3 | "body-parser": "registry:dt/body-parser#0.0.0+20160317120654", 4 | "chai": "registry:dt/chai#3.4.0+20160317120654", 5 | "compression": "registry:dt/compression#0.0.0+20160317120654", 6 | "cookie-parser": "registry:dt/cookie-parser#1.3.4+20160316155526", 7 | "express": "registry:dt/express#4.0.0+20160317120654", 8 | "express-serve-static-core": "registry:dt/express-serve-static-core#0.0.0+20160322035842", 9 | "express-session": "registry:dt/express-session#0.0.0+20160331200931", 10 | "lodash": "registry:dt/lodash#3.10.0+20160330154726", 11 | "mime": "registry:dt/mime#0.0.0+20160316155526", 12 | "mkdirp": "registry:dt/mkdirp#0.3.0+20160317120654", 13 | "mocha": "registry:dt/mocha#2.2.5+20160317120654", 14 | "morgan": "registry:dt/morgan#1.2.2+20160317120654", 15 | "mysql": "registry:dt/mysql#0.0.0+20160316155526", 16 | "node": "registry:dt/node#4.0.0+20160330064709", 17 | "sequelize": "registry:dt/sequelize#3.4.1+20160317120654", 18 | "serve-static": "registry:dt/serve-static#0.0.0+20160317120654", 19 | "validator": "registry:dt/validator#4.5.1+20160316155526", 20 | "winston": "registry:dt/winston#0.0.0+20160324163241" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sequelize-typescript-examples", 3 | "version": "0.0.1", 4 | "description": "Sequelize examples in TypeScript", 5 | "author": "Suksant Sae Lor (Hui) ", 6 | "scripts": { 7 | "clean": "gulp clean", 8 | "compile": "gulp compile", 9 | "postinstall": "gulp compile", 10 | "start": "gulp start", 11 | "typings": "gulp typings", 12 | "test": "gulp compile && mocha 'build/*/test/**/*.js'" 13 | }, 14 | "dependencies": { 15 | "body-parser": "^1.15.0", 16 | "compression": "^1.6.1", 17 | "continuation-local-storage": "^3.1.6", 18 | "cookie-parser": "^1.4.1", 19 | "express": "^4.13.4", 20 | "express-session": "^1.13.0", 21 | "lodash": "^4.8.1", 22 | "mkdirp": "^0.5.1", 23 | "morgan": "^1.7.0", 24 | "mysql": "^2.10.2", 25 | "sequelize": "^3.21.0", 26 | "sequelize-cli": "^2.3.1", 27 | "winston": "^2.2.0" 28 | }, 29 | "devDependencies": { 30 | "chai": "^3.5.0", 31 | "del": "^2.2.0", 32 | "gulp": "^3.9.1", 33 | "gulp-help": "^1.6.1", 34 | "gulp-load-plugins": "^1.2.0", 35 | "gulp-typescript": "^2.12.2", 36 | "gulp-typings": "^1.3.0", 37 | "gulp-util": "^3.0.7", 38 | "mocha": "^2.4.5", 39 | "typescript": "^1.8.9", 40 | "yargs": "^4.3.2" 41 | }, 42 | "engines": { 43 | "node": ">=4.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | 4 | let args = require("yargs").argv; 5 | let compression = require("compression"); 6 | let del = require("del"); 7 | let gulp = require("gulp"); 8 | let plugins = require("gulp-load-plugins")(); 9 | let spawn = require("child_process").spawn; 10 | 11 | plugins.help(gulp); 12 | 13 | gulp.task("clean", "Clean the distribution.", [], clean); 14 | gulp.task("compile", "Compile typescript files.", ["typings"], compile); 15 | gulp.task("start", "Compile the selected example and start the server.", [], start); 16 | gulp.task("typings", "Install type definitions.", [], typings); 17 | 18 | let tsProject = plugins.typescript.createProject("./tsconfig.json"); 19 | 20 | function compile() { 21 | let tsResult = tsProject.src().pipe(plugins.typescript(tsProject), {typescript: require("typescript")}); 22 | return tsResult.js.pipe(gulp.dest("./build")); 23 | } 24 | 25 | function clean() { 26 | return del(["./build", "./typings"]); 27 | } 28 | 29 | function start() { 30 | if (args.example) { 31 | let options = [`./build/${args.example}/src/server.js`]; 32 | spawn("node", options, {stdio: "inherit"}); 33 | } else { 34 | plugins.util.log(`No example specified. Run: "npm start -- --example="`); 35 | } 36 | } 37 | 38 | function typings() { 39 | return gulp.src("./typings.json").pipe(plugins.typings()); 40 | } 41 | }()); 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/suksant/sequelize-typescript-examples.svg?branch=master)](https://travis-ci.org/suksant/sequelize-typescript-examples) 2 | 3 | # Sequelize TypeScript Examples 4 | 5 | This repository demonstrates examples of using Sequelize in TypeScript projects. Although I try to keep the code clean with consistent conventions, it is by no means considered as best practice. The implemented logic focuses around how to enhance the experience of using Sequelize in TypeScript projects. 6 | 7 | ## Setup 8 | 9 | Configure your database parameters in `configs/database-config.ts`. You must configure `username`, `password` and `database` (database name). Other parameters can be adjusted as required. 10 | 11 | Install all required dependencies: 12 | ``` 13 | npm install 14 | ``` 15 | 16 | > Note: If `tsconfig.json` is overridden before the above command is executed, the compilation may fail. 17 | 18 | Run the project example you want: 19 | ``` 20 | npm start -- --example="sequelize-express" 21 | ``` 22 | 23 | The above command will run the example from `build/sequelize-express`. 24 | 25 | Perform CRUD operations to test it. To get a list of products, for example, run: 26 | ``` 27 | curl -X GET http://localhost:3000/api/products 28 | ``` 29 | 30 | For simplicity, the `configs` are shared between different example projects. 31 | 32 | If you change the configurations, you will need to run `npm run compile` for it to take effect. 33 | 34 | If you want to clean the build, run `npm run clean` or `gulp clean` if you have gulp installed globally. 35 | 36 | ## Test 37 | ``` 38 | npm test 39 | ``` 40 | -------------------------------------------------------------------------------- /sequelize-express/src/routers/product-router.ts: -------------------------------------------------------------------------------- 1 | import {productService} from "../services/product-service"; 2 | import {ProductInstance} from "../models/interfaces/product-interface"; 3 | import {Request, Response, Router} from "express"; 4 | 5 | export const router = Router(); 6 | 7 | router.post("/", (req: Request, res: Response) => { 8 | productService.createProduct(req.body).then((product: ProductInstance) => { 9 | return res.status(201).send(product); 10 | }).catch((error: Error) => { 11 | return res.status(409).send(error); 12 | }); 13 | }); 14 | 15 | router.get("/:name", (req: Request, res: Response) => { 16 | productService.retrieveProduct(req.params.name).then((product: ProductInstance) => { 17 | if (product) { 18 | return res.send(product); 19 | } else { 20 | return res.sendStatus(404); 21 | } 22 | }).catch((error: Error) => { 23 | return res.status(500).send(error); 24 | }); 25 | }); 26 | 27 | router.get("/", (req: Request, res: Response) => { 28 | productService.retrieveProducts().then((products: Array) => { 29 | return res.send(products); 30 | }).catch((error: Error) => { 31 | return res.status(500).send(error); 32 | }); 33 | }); 34 | 35 | router.post("/:name", (req: Request, res: Response) => { 36 | productService.updateProduct(req.params.name, req.body).then(() => { 37 | return res.sendStatus(200); 38 | }).catch((error: Error) => { 39 | return res.status(409).send(error); 40 | }); 41 | }); 42 | 43 | router.delete("/:name", (req: Request, res: Response) => { 44 | productService.deleteProduct(req.params.name).then(() => { 45 | return res.sendStatus(200); 46 | }).catch((error: Error) => { 47 | return res.status(500).send(error); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /sequelize-express/src/models/index.ts: -------------------------------------------------------------------------------- 1 | import * as cls from "continuation-local-storage"; 2 | import * as fs from "fs"; 3 | import * as path from "path"; 4 | import * as SequelizeStatic from "sequelize"; 5 | import {configs} from "../../../configs/configs"; 6 | import {logger} from "../utils/logger"; 7 | import {ProductAttributes, ProductInstance} from "./interfaces/product-interface"; 8 | import {Sequelize} from "sequelize"; 9 | 10 | export interface SequelizeModels { 11 | Product: SequelizeStatic.Model; 12 | } 13 | 14 | class Database { 15 | private _basename: string; 16 | private _models: SequelizeModels; 17 | private _sequelize: Sequelize; 18 | 19 | constructor() { 20 | this._basename = path.basename(module.filename); 21 | let dbConfig = configs.getDatabaseConfig(); 22 | 23 | if (dbConfig.logging) { 24 | dbConfig.logging = logger.info; 25 | } 26 | 27 | (SequelizeStatic as any).cls = cls.createNamespace("sequelize-transaction"); 28 | this._sequelize = new SequelizeStatic(dbConfig.database, dbConfig.username, 29 | dbConfig.password, dbConfig); 30 | this._models = ({} as any); 31 | 32 | fs.readdirSync(__dirname).filter((file: string) => { 33 | return (file !== this._basename) && (file !== "interfaces"); 34 | }).forEach((file: string) => { 35 | let model = this._sequelize.import(path.join(__dirname, file)); 36 | this._models[(model as any).name] = model; 37 | }); 38 | 39 | Object.keys(this._models).forEach((modelName: string) => { 40 | if (typeof this._models[modelName].associate === "function") { 41 | this._models[modelName].associate(this._models); 42 | } 43 | }); 44 | } 45 | 46 | getModels() { 47 | return this._models; 48 | } 49 | 50 | getSequelize() { 51 | return this._sequelize; 52 | } 53 | } 54 | 55 | const database = new Database(); 56 | export const models = database.getModels(); 57 | export const sequelize = database.getSequelize(); 58 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "isolatedModules": false, 7 | "jsx": "react", 8 | "experimentalDecorators": true, 9 | "emitDecoratorMetadata": true, 10 | "declaration": false, 11 | "noImplicitAny": false, 12 | "noImplicitUseStrict": false, 13 | "removeComments": true, 14 | "noLib": false, 15 | "preserveConstEnums": true, 16 | "suppressImplicitAnyIndexErrors": true, 17 | "rootDir": "." 18 | }, 19 | "filesGlob": [ 20 | "**/*.ts", 21 | "custom-typings/**/*.d.ts", 22 | "typings/main.d.ts", 23 | "typings/main/**/*.ts", 24 | "!typings/browser.d.ts", 25 | "!typings/browser/**/*.ts", 26 | "!node_modules/**" 27 | ], 28 | "compileOnSave": false, 29 | "buildOnSave": false, 30 | "files": [ 31 | "configs/configs.ts", 32 | "configs/database-config.ts", 33 | "configs/logging-config.ts", 34 | "configs/server-config.ts", 35 | "custom-typings/continuation-local-storage/continuation-local-storage.d.ts", 36 | "sequelize-express/src/models/index.ts", 37 | "sequelize-express/src/models/interfaces/product-interface.ts", 38 | "sequelize-express/src/models/product-model.ts", 39 | "sequelize-express/src/routers/product-router.ts", 40 | "sequelize-express/src/server.ts", 41 | "sequelize-express/src/services/product-service.ts", 42 | "sequelize-express/src/utils/logger.ts", 43 | "sequelize-express/test/services/product-service.ts", 44 | "typings/main.d.ts", 45 | "typings/main/ambient/body-parser/index.d.ts", 46 | "typings/main/ambient/chai/index.d.ts", 47 | "typings/main/ambient/compression/index.d.ts", 48 | "typings/main/ambient/cookie-parser/index.d.ts", 49 | "typings/main/ambient/express-serve-static-core/index.d.ts", 50 | "typings/main/ambient/express-session/index.d.ts", 51 | "typings/main/ambient/express/index.d.ts", 52 | "typings/main/ambient/lodash/index.d.ts", 53 | "typings/main/ambient/mime/index.d.ts", 54 | "typings/main/ambient/mkdirp/index.d.ts", 55 | "typings/main/ambient/mocha/index.d.ts", 56 | "typings/main/ambient/morgan/index.d.ts", 57 | "typings/main/ambient/mysql/index.d.ts", 58 | "typings/main/ambient/node/index.d.ts", 59 | "typings/main/ambient/sequelize/index.d.ts", 60 | "typings/main/ambient/serve-static/index.d.ts", 61 | "typings/main/ambient/validator/index.d.ts", 62 | "typings/main/ambient/winston/index.d.ts" 63 | ], 64 | "atom": { 65 | "rewriteTsconfig": true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /sequelize-express/src/server.ts: -------------------------------------------------------------------------------- 1 | import * as bodyParser from "body-parser"; 2 | import * as cluster from "cluster"; 3 | import * as compression from "compression"; 4 | import * as cookieParser from "cookie-parser"; 5 | import * as express from "express"; 6 | import * as http from "http"; 7 | import * as morgan from "morgan"; 8 | import * as os from "os"; 9 | import * as path from "path"; 10 | import {configs} from "../../configs/configs"; 11 | import {logger, skip, stream} from "./utils/logger"; 12 | import {router as productRouter} from "./routers/product-router"; 13 | import {sequelize} from "./models/index"; 14 | import {Express, Request, Response} from "express"; 15 | import {Worker} from "cluster"; 16 | 17 | interface ServerAddress { 18 | address: string; 19 | port: number; 20 | addressType: string; 21 | } 22 | 23 | class Server { 24 | private _app: Express; 25 | private _server: http.Server; 26 | 27 | constructor() { 28 | this._app = express(); 29 | this._app.use(compression()); 30 | this._app.use(bodyParser.json()); 31 | this._app.use(cookieParser()); 32 | this._app.use(express.static(path.join(__dirname, "./public"))); 33 | this._app.use(morgan("combined", {skip: skip, stream: stream})); 34 | this._app.use((error: Error, req: Request, res: Response, next: Function) => { 35 | if (error) { 36 | res.status(400).send(error); 37 | } 38 | }); 39 | this._app.use("/api/products", productRouter); 40 | this._server = http.createServer(this._app); 41 | } 42 | 43 | private _onError(error: NodeJS.ErrnoException): void { 44 | if (error.syscall) { 45 | throw error; 46 | } 47 | 48 | let port = configs.getServerConfig().port; 49 | let bind = `Port ${port}`; 50 | 51 | switch (error.code) { 52 | case "EACCES": 53 | logger.error(`[EACCES] ${bind} requires elevated privileges.`); 54 | process.exit(1); 55 | break; 56 | case "EADDRINUSE": 57 | logger.error(`[EADDRINUSE] ${bind} is already in use.`); 58 | process.exit(1); 59 | break; 60 | default: 61 | throw error; 62 | } 63 | }; 64 | 65 | private _onListening(): void { 66 | let address = this._server.address(); 67 | let bind = `port ${address.port}`; 68 | logger.info(`Listening on ${bind}.`); 69 | }; 70 | 71 | start(): void { 72 | if (cluster.isMaster) { 73 | sequelize.sync().then(() => { 74 | logger.info("Database synced."); 75 | 76 | for (let c = 0; c < os.cpus().length; c++) { 77 | cluster.fork(); 78 | } 79 | 80 | cluster.on("exit", (worker: Worker, code: number, signal: string) => { 81 | logger.info(`Worker ${worker.process.pid} died.`); 82 | }); 83 | 84 | cluster.on("listening", (worker: Worker, address: ServerAddress) => { 85 | logger.info(`Worker ${worker.process.pid} connected to port ${address.port}.`); 86 | }); 87 | }).catch((error: Error) => { 88 | logger.error(error.message); 89 | }); 90 | } else { 91 | this._server.listen(3000); 92 | this._server.on("error", error => this._onError(error)); 93 | this._server.on("listening", () => this._onListening()); 94 | } 95 | } 96 | 97 | stop(): void { 98 | this._server.close(); 99 | process.exit(0); 100 | } 101 | } 102 | 103 | let server = new Server(); 104 | server.start(); 105 | process.on("SIGINT", () => { 106 | server.stop(); 107 | }); 108 | -------------------------------------------------------------------------------- /sequelize-express/src/services/product-service.ts: -------------------------------------------------------------------------------- 1 | import {logger} from "../utils/logger"; 2 | import {models, sequelize} from "../models/index"; 3 | import {ProductAttributes, ProductInstance} from "../models/interfaces/product-interface"; 4 | import {Transaction} from "sequelize"; 5 | 6 | export class ProductService { 7 | createProduct(productAttributes: ProductAttributes): Promise { 8 | let promise = new Promise((resolve: Function, reject: Function) => { 9 | sequelize.transaction((t: Transaction) => { 10 | return models.Product.create(productAttributes).then((product: ProductInstance) => { 11 | logger.info(`Created product with name ${productAttributes.name}.`); 12 | resolve(product); 13 | }).catch((error: Error) => { 14 | logger.error(error.message); 15 | reject(error); 16 | }); 17 | }); 18 | }); 19 | 20 | return promise; 21 | } 22 | 23 | retrieveProduct(name: string): Promise { 24 | let promise = new Promise((resolve: Function, reject: Function) => { 25 | sequelize.transaction((t: Transaction) => { 26 | return models.Product.findOne({where: {name: name}}).then((product: ProductInstance) => { 27 | if (product) { 28 | logger.info(`Retrieved product with name ${name}.`); 29 | } else { 30 | logger.info(`Product with name ${name} does not exist.`); 31 | } 32 | resolve(product); 33 | }).catch((error: Error) => { 34 | logger.error(error.message); 35 | reject(error); 36 | }); 37 | }); 38 | }); 39 | 40 | return promise; 41 | } 42 | 43 | retrieveProducts(): Promise> { 44 | let promise = new Promise>((resolve: Function, reject: Function) => { 45 | sequelize.transaction((t: Transaction) => { 46 | return models.Product.findAll().then((products: Array) => { 47 | logger.info("Retrieved all products."); 48 | resolve(products); 49 | }).catch((error: Error) => { 50 | logger.error(error.message); 51 | reject(error); 52 | }); 53 | }); 54 | }); 55 | 56 | return promise; 57 | } 58 | 59 | updateProduct(name: string, productAttributes: any): Promise { 60 | let promise = new Promise((resolve: Function, reject: Function) => { 61 | sequelize.transaction((t: Transaction) => { 62 | return models.Product.update(productAttributes, {where: {name: name}}) 63 | .then((results: [number, Array]) => { 64 | if (results.length > 0) { 65 | logger.info(`Updated product with name ${name}.`); 66 | } else { 67 | logger.info(`Product with name ${name} does not exist.`); 68 | } 69 | resolve(null); 70 | }).catch((error: Error) => { 71 | logger.error(error.message); 72 | reject(error); 73 | }); 74 | }); 75 | }); 76 | 77 | return promise; 78 | } 79 | 80 | deleteProduct(name: string): Promise { 81 | let promise = new Promise((resolve: Function, reject: Function) => { 82 | sequelize.transaction((t: Transaction) => { 83 | return models.Product.destroy({where: {name: name}}).then((afffectedRows: number) => { 84 | if (afffectedRows > 0) { 85 | logger.info(`Deleted product with name ${name}`); 86 | } else { 87 | logger.info(`Product with name ${name} does not exist.`); 88 | } 89 | resolve(null); 90 | }).catch((error: Error) => { 91 | logger.error(error.message); 92 | reject(error); 93 | }); 94 | }); 95 | }); 96 | 97 | return promise; 98 | } 99 | } 100 | 101 | export const productService = new ProductService(); 102 | -------------------------------------------------------------------------------- /sequelize-express/test/services/product-service.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable:no-require-imports */ 2 | 3 | import {expect} from "chai"; 4 | import {sequelize} from "../../src/models/index"; 5 | import {ProductInstance} from "../../src/models/interfaces/product-interface"; 6 | import {ProductService} from "../../src/services/product-service"; 7 | 8 | const delay = 500; 9 | 10 | describe("ProductService", () => { 11 | const productAttributes = { 12 | name: "product1", 13 | description: "Description of product1." 14 | }; 15 | 16 | describe("#createProduct", () => { 17 | let productService: ProductService; 18 | 19 | before((done: Function) => { 20 | setTimeout(() => { 21 | sequelize.sync().then(() => { 22 | let service = require("../../src/services/product-service"); 23 | productService = service.productService; 24 | done(); 25 | }).catch((error: Error) => { 26 | done(error); 27 | }); 28 | }, delay); 29 | }); 30 | 31 | it("should create a product in the database correctly", () => { 32 | productService.createProduct(productAttributes).then((product: ProductInstance) => { 33 | expect(product.dataValues.name).to.equals(productAttributes.name); 34 | expect(product.dataValues.description).to.equals(productAttributes.description); 35 | }).catch((error: Error) => { 36 | throw error; 37 | }); 38 | }); 39 | }); 40 | 41 | describe("#retrieveProduct(s)", () => { 42 | let productService: ProductService; 43 | 44 | before((done: Function) => { 45 | setTimeout(() => { 46 | sequelize.sync().then(() => { 47 | let service = require("../../src/services/product-service"); 48 | productService = service.productService; 49 | productService.createProduct(productAttributes).then((product: ProductInstance) => { 50 | done(); 51 | }).catch((error: Error) => { 52 | done(error); 53 | }); 54 | }).catch((error: Error) => { 55 | done(error); 56 | }); 57 | }, delay); 58 | }); 59 | 60 | it("should retrieve a product from the database correctly", () => { 61 | productService.retrieveProduct(productAttributes.name).then((product: ProductInstance) => { 62 | expect(product.dataValues.name).to.equals(productAttributes.name); 63 | expect(product.dataValues.description).to.equals(productAttributes.description); 64 | }).catch((error: Error) => { 65 | throw error; 66 | }); 67 | }); 68 | 69 | it("should retrieve the correct number of products", () => { 70 | productService.retrieveProducts().then((products: Array) => { 71 | expect(products.length).to.equals(1); 72 | }); 73 | }); 74 | }); 75 | 76 | describe("#updateProduct", () => { 77 | let productService: ProductService; 78 | 79 | before((done: Function) => { 80 | setTimeout(() => { 81 | sequelize.sync().then(() => { 82 | let service = require("../../src/services/product-service"); 83 | productService = service.productService; 84 | productService.createProduct(productAttributes).then((product: ProductInstance) => { 85 | done(); 86 | }).catch((error: Error) => { 87 | done(error); 88 | }); 89 | }).catch((error: Error) => { 90 | done(error); 91 | }); 92 | }, delay); 93 | }); 94 | 95 | it("should update the product attribute(s) correctly", () => { 96 | let updateAttributes = { 97 | description: "Update description of product1." 98 | }; 99 | productService.updateProduct(productAttributes.name, updateAttributes).then(() => { 100 | productService.retrieveProduct(productAttributes.name).then((product: ProductInstance) => { 101 | expect(product.dataValues.name).to.equals(productAttributes.name); 102 | expect(product.dataValues.description).to.equals(updateAttributes.description); 103 | }).catch((error: Error) => { 104 | throw error; 105 | }); 106 | }).catch((error: Error) => { 107 | throw error; 108 | }); 109 | }); 110 | }); 111 | 112 | describe("#deleteProduct", () => { 113 | let productService: ProductService; 114 | 115 | before((done: Function) => { 116 | setTimeout(() => { 117 | sequelize.sync().then(() => { 118 | let service = require("../../src/services/product-service"); 119 | productService = service.productService; 120 | productService.createProduct(productAttributes).then((product: ProductInstance) => { 121 | done(); 122 | }).catch((error: Error) => { 123 | done(error); 124 | }); 125 | }).catch((error: Error) => { 126 | done(error); 127 | }); 128 | }, delay); 129 | }); 130 | 131 | it("should delete the product from the database correctly", () => { 132 | productService.deleteProduct(productAttributes.name).then(() => { 133 | productService.retrieveProduct(productAttributes.name).then((product: ProductInstance) => { 134 | expect(product).to.be.null; 135 | }).catch((error: Error) => { 136 | throw error; 137 | }); 138 | }).catch((error: Error) => { 139 | throw error; 140 | }); 141 | }); 142 | }); 143 | }); 144 | --------------------------------------------------------------------------------