├── services └── .gitkeep ├── .gitignore ├── .env.sample ├── controllers └── index.js ├── .sequelizerc ├── store └── db │ ├── config.js │ ├── options.js │ ├── models │ └── Account.js │ └── index.js ├── server.js ├── scripts └── init-database.js ├── app.js ├── package.json ├── middlewares └── response.js └── README.md /services/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | .DS_Store 4 | views/cache 5 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | PORT=6060 2 | DATABASE_URL=db_protocol://username:password@domain.com/database_name 3 | -------------------------------------------------------------------------------- /controllers/index.js: -------------------------------------------------------------------------------- 1 | exports.GET = function (req, res) { 2 | res.data({ 3 | message: 'Hello world!' 4 | }); 5 | }; 6 | -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "config": "store/db/config.js", 3 | "models-path": "store/db/models", 4 | "seeders-path": "store/db/seeders", 5 | "migrations-path": "store/db/migrations" 6 | }; 7 | -------------------------------------------------------------------------------- /store/db/config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const options = require('./options'); 4 | 5 | module.exports = { 6 | use_env_variable: 'DATABASE_URL', 7 | seederStorage: 'json', 8 | seederStoragePath: 'store/db/seedData.json', 9 | options 10 | }; 11 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | var app = require('./app'); 4 | 5 | var port = process.env.PORT; 6 | var startTime = new Date(); 7 | 8 | app.listen(port, function() { 9 | console.log("Server start at %s. Listening on %d", startTime, port); 10 | }); 11 | -------------------------------------------------------------------------------- /store/db/options.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // default options for table definition 3 | define: { 4 | // disable the modification of tablenames; By default, sequelize will automatically 5 | // transform all passed model names (first parameter of define) into plural. 6 | // if you don't want that, set the following 7 | freezeTableName: true 8 | } 9 | }; 10 | -------------------------------------------------------------------------------- /scripts/init-database.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var db = require('../store/postgres'); 4 | 5 | if (process.env.NODE_ENV === 'production') { 6 | console.error('This script is forbidden to run in production.'); 7 | return process.exit(1); 8 | } 9 | 10 | db.sync({ 11 | force: true 12 | }).then(function (result) { 13 | console.log('resolved'); 14 | // console.log(result); 15 | process.exit(0); 16 | }, function (reason) { 17 | console.log('rejected'); 18 | console.log(reason); 19 | process.exit(1); 20 | }); 21 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var rainbow = require('rainbow'); 3 | var cookieParser = require('cookie-parser'); 4 | var bodyParser = require('body-parser'); 5 | 6 | 7 | 8 | var app = express(); 9 | 10 | app.set('x-powered-by', false); 11 | 12 | app.use(cookieParser()); 13 | app.use(bodyParser.urlencoded({ 14 | extended: true 15 | })); 16 | app.use(bodyParser.json()); 17 | 18 | app.use(require('./middlewares/response')); 19 | 20 | // routes managed by rainbow 21 | app.use(rainbow()); 22 | 23 | module.exports = app; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-bootstrap", 3 | "description": "express example app", 4 | "version": "1.0.0", 5 | "main": "app.js", 6 | "private": true, 7 | "scripts": { 8 | "dev": "nodemon server.js", 9 | "init-databases": "./scripts/init-database.js" 10 | }, 11 | "dependencies": { 12 | "body-parser": "1.19.0", 13 | "cookie-parser": "1.4.4", 14 | "dotenv": "8.1.0", 15 | "express": "4.x", 16 | "rainbow": "2.4.1", 17 | "sequelize": "5.18.4" 18 | }, 19 | "devDependencies": { 20 | "nodemon": "1.19.2", 21 | "sequelize-cli": "5.5.1" 22 | }, 23 | "engines": { 24 | "node": "^8.x" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /store/db/models/Account.js: -------------------------------------------------------------------------------- 1 | module.exports = function (dbc, Sequelize) { 2 | return dbc.define('Account', { 3 | 'username': { 4 | type: Sequelize.STRING, 5 | unique: true, 6 | validate: { 7 | is: ['\\w[\\w\\.-]*\\w', 'i'], 8 | notNull: true, 9 | len: [2, 40] 10 | } 11 | }, 12 | 13 | 'password': { 14 | type: Sequelize.STRING, 15 | validate: { 16 | notNull: true, 17 | len: [6, 40] 18 | } 19 | }, 20 | 21 | 'email': { 22 | type: Sequelize.STRING, 23 | unique: true, 24 | validate: { 25 | isEmail: true, 26 | notNull: true, 27 | len: [6, 80] 28 | } 29 | } 30 | }); 31 | }; 32 | -------------------------------------------------------------------------------- /store/db/index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | 4 | require('dotenv').config(); 5 | 6 | var Sequelize = require('sequelize'); 7 | 8 | var rc = require('../../.sequelizerc'); 9 | var options = require('./options'); 10 | 11 | var db = new Sequelize(process.env.DATABASE_URL, options); 12 | 13 | console.debug('Database configuration initialized.') 14 | 15 | fs.readdirSync(path.join(process.env.PWD, rc['models-path'])).forEach(function (file) { 16 | if (!file.startsWith('.')) { 17 | db['import'](file); 18 | 19 | console.debug(`SQL table definition ${file} imported.`); 20 | } 21 | }); 22 | 23 | module.exports = db; 24 | -------------------------------------------------------------------------------- /middlewares/response.js: -------------------------------------------------------------------------------- 1 | const extension = { 2 | // 200 3 | data(data) { 4 | this.format({ 5 | 'json': () => { 6 | this.status(200).json(data); 7 | } 8 | }); 9 | 10 | return this; 11 | }, 12 | 13 | // 201 14 | created(data) { 15 | this.status(201).json(data); 16 | 17 | return this; 18 | }, 19 | 20 | // 204 21 | ok() { 22 | this.sendStatus(204); 23 | 24 | return this; 25 | }, 26 | 27 | // 205 28 | done() { 29 | this.sendStatus(205); 30 | 31 | return this; 32 | }, 33 | 34 | // 400 35 | badrequest() { 36 | this.sendStatus(400); 37 | 38 | return this; 39 | }, 40 | 41 | // 401 42 | unauthorized() { 43 | this.sendStatus(401); 44 | 45 | return this; 46 | }, 47 | 48 | // 403 49 | forbidden() { 50 | this.sendStatus(403); 51 | 52 | return this; 53 | }, 54 | 55 | // 404 56 | notfound() { 57 | this.sendStatus(404); 58 | 59 | return this; 60 | }, 61 | 62 | // 409 63 | conflict() { 64 | this.sendStatus(409); 65 | 66 | return this; 67 | }, 68 | 69 | // 422 70 | invalid(fields) { 71 | this.status(422); 72 | if (fields) { 73 | this.json(fields); 74 | } 75 | 76 | return this; 77 | }, 78 | 79 | // 500 80 | error(err) { 81 | this.status(500).send(err || ''); 82 | 83 | return this; 84 | } 85 | }; 86 | 87 | module.exports = function (req, res, next) { 88 | for (var method in extension) { 89 | res[method] = extension[method]; 90 | } 91 | 92 | next(null, req, res); 93 | }; 94 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Express Bootstrap 2 | ================= 3 | 4 | Bootstrap for Node.js RESTful web applications based on [Express][]. 5 | 6 | Know before start 7 | ---------- 8 | 9 | Before you begin to develop, make sure you have known something about these: 10 | 11 | 0. JavaScript 12 | 0. [Node.js](http://nodejs.org/api/) 13 | 0. [Express][]: node.js web application framework 14 | 0. [Sequelize][]: node.js database ORM middleware 15 | 0. Relational Database: such as MySQL or PostgreSQL 16 | 17 | Local deployment 18 | ---------- 19 | 20 | You need run these steps just once. 21 | 22 | 0. First, you need to use `npm install` to download all depended pacakges. 23 | 24 | $ npm install 25 | 26 | 0. Copy `.env.sample` to `.env`, and configure your database URL in it, also set local HTTP port you like. The `db_protocol` could be `mysql` or `postgres` or any other your are using. 27 | 28 | 0. Use this command to initilize database tables (make sure you've installed MySQL or Postgresql): 29 | 30 | $ npm run init-databases 31 | 32 | 0. Start app by this command: 33 | 34 | $ npm run dev 35 | 36 | The `nodemon` will be used to watch any modifications. 37 | 38 | ## Architecture ## 39 | 40 | ### Folder structure ### 41 | 42 | . 43 | |-- controllers/ # All HTTP API routers, same path as folder 44 | |-- middlewares/ # Interceptor filters before routers 45 | |-- services/ # Main business logic code as service layer 46 | |-- store/ # Database definition 47 | | |-- db/ 48 | | | |-- migrations/ # Sequelize migration files 49 | | | |-- models/ # Sequelize database table definition 50 | | `-- ... # Other database driver definition 51 | |-- utils/ # Local utilities library for application 52 | |-- .env.sample # Local environment config sample 53 | |-- app.js # Application main file, which could be exported as a module 54 | `-- server.js # Npm default start script 55 | 56 | ### Back end MVC ### 57 | 58 | #### Model #### 59 | 60 | All models are about database tables, and use [Sequelize][] ORM framework to manage them in `store//` folder. 61 | 62 | Every file in `store//` folder defined a database table, use **camel case** table name, excluding a special file `index.js` which maintains all tables associations (1 to 1, 1 to many, many to many) in it. 63 | 64 | #### Controller(action) #### 65 | 66 | Just like `Action` layer in Java SSH, or controller in PHP CI, here controllers' responsibility as routing and catching HTTP request from client. 67 | 68 | All files under `controllers/` folder would have same path when client request. For example, `controllers/index.js` would catch request from `http://yourhost/`, `controllers/account/login.js` would catch request from `http://yourhost/account/login`. 69 | 70 | In order to do some pre-treatment before real action process, there designed a filter layer just like interceptors in Java SSH. And it is easy to maintain in `middlewares/` folder, each file in it is an [Express][] middleware. 71 | 72 | If a controller need to be intercepted by some filters, it should be define like this: 73 | 74 | // real controller process 75 | exports.GET = [ 76 | authoriticate, 77 | function (req, res, next) { 78 | // ... 79 | res.send(200, 'OK'); 80 | } 81 | ]; 82 | 83 | All controllers routing had been move to a single npm. See more about [Rainbow](https://github.com/mytharcher/rainbow). 84 | 85 | All these configuration could be changed in `app.js`. 86 | 87 | ### RESTful HTTP Response ### 88 | 89 | Here is some HTTP status code used for certain meaning of response from server. And the methods in this list have been implemented in response object. 90 | 91 | * **200** `data` 92 | 93 | Everything is OK, and responese contains some data from server. 94 | 95 | * **201**(\*) `created` 96 | 97 | Create done, and response new created resources. 98 | 99 | May use for creating new data record. 100 | 101 | * **204** `ok` 102 | 103 | A certain operation has been done successfully, and no extra data need to response. 104 | 105 | Used for updating or deleting some data. 106 | 107 | * **205**(\*) `done` 108 | 109 | Almost same as `204`, but need to update view. 110 | 111 | May use in login/logout, etc. 112 | 113 | * **400** `badrequest` 114 | 115 | Syntax error. 116 | 117 | Query parameters less than required or data type not match. 118 | 119 | * **403** `forbidden` 120 | 121 | Forbidden. Just indicates that the request need authorization. 122 | 123 | * **404** `notfound` 124 | 125 | Not found. Wrong path or resouces not exist (may be private). 126 | 127 | * **409** `conflict` 128 | 129 | Confict. Repeat record. 130 | 131 | * **422**(\*) `wrongcontent` 132 | 133 | Semantics error. 134 | 135 | Something like `400`, additionally indicates query parameters contains wrong content. 136 | 137 | * **500** `servererror` 138 | 139 | Server error. There is something wrong in server. 140 | 141 | Mostly indicates that server exceptions have not been caught. 142 | 143 | These all could be used in both filters and controllers. 144 | 145 | *Notice*: \* means it is just in design. 146 | 147 | For more infomation [wiki:HTTP status codes](http://en.wikipedia.org/wiki/HTTP_status_code). 148 | 149 | -EOF- 150 | 151 | [Express]: http://expressjs.com/ 152 | [Sequelize]: http://www.sequelizejs.com/ 153 | --------------------------------------------------------------------------------