├── .env ├── .gitignore ├── app.js ├── config.js ├── controllers └── home.js ├── docker-compose.yml ├── frontend ├── index.js ├── js │ └── .gitkeep ├── jsx │ └── .gitkeep └── less │ └── .gitkeep ├── models ├── init.js └── posts.js ├── package.json ├── public ├── .gitkeep └── images │ └── chase.png ├── readme.md ├── var ├── docker │ ├── mysql │ │ └── .gitkeep │ └── redis │ │ └── .gitkeep └── sample_schema.sql ├── views ├── home │ └── home.twig └── master.twig └── webpack.config.js /.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=expressmvc 2 | APPENV=development 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules 3 | var/docker/mysql/* 4 | !var/docker/mysql/.gitkeep 5 | var/docker/redis/* 6 | !var/docker/redis/.gitkeep 7 | public/main.bundle.js 8 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | require('nocamel'); 2 | 3 | // global config 4 | config = require('./config'); 5 | 6 | // set up express 7 | const express = require('express'); 8 | const redis = require('redis'); 9 | const session = require('express-session'); 10 | const store = require('connect-redis')(session); 11 | const app = express(); 12 | 13 | let redis_client = redis.createClient({ host: 'redis', port: 6379 }); 14 | 15 | app.set('view engine', 'twig'); 16 | app.set('views', './views'); 17 | app.use(express.urlencoded({ extended: true })); 18 | app.use(express.static('./public')); 19 | app.use(session({ 20 | store: new store({ client: redis_client }), 21 | secret: 'whatever', 22 | resave: false, 23 | saveUninitialized: true 24 | })); 25 | 26 | // global database object 27 | db = require('./models/init'); 28 | 29 | // each controller file 30 | const home = require('./controllers/home'); 31 | 32 | // your routes 33 | app.get('/', home.home); // routes url / to controllers/home.js home method 34 | app.get('/home', home.home); // routes url /home to controllers/home.js home method 35 | 36 | // start the app 37 | app.listen(8000, () => {}); 38 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | environment: process.env.APPENV || 'development', 4 | database: { 5 | username: 'root', 6 | password: 'root', 7 | database: 'testdb', 8 | host: 'mysql', 9 | dialect: 'mysql', 10 | logging: false, 11 | timezone: '+00:00', 12 | define: { 13 | underscored: true, 14 | timestamps: false 15 | } 16 | } 17 | 18 | }; 19 | -------------------------------------------------------------------------------- /controllers/home.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | async home(req, res) { 4 | let posts = await db.posts 5 | .find_all(); 6 | 7 | // this says: render the views/home/home.twig file and give it the posts variable to use 8 | return res.render('home/home', { 9 | posts 10 | }); 11 | } 12 | 13 | }; 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | container_name: expressmvc_app 5 | image: node:14.4.0 6 | ports: 7 | - "8000:8000" 8 | volumes: 9 | - ./:/opt/src 10 | command: bash -c 'cd /opt/src && npm install && npm start' 11 | environment: 12 | APPENV: ${APPENV} 13 | mysql: 14 | container_name: expressmvc_mysql 15 | image: mysql:8.0 16 | environment: 17 | MYSQL_ROOT_PASSWORD: root 18 | MYSQL_DATABASE: testdb 19 | command: --init-file /var/lib/init/sample_schema.sql 20 | volumes: 21 | - ./:/opt/src 22 | - ./var/docker/mysql:/var/lib/mysql 23 | - ./var/sample_schema.sql:/var/lib/init/sample_schema.sql 24 | redis: 25 | container_name: expressmvc_redis 26 | image: redis:5.0.3 27 | volumes: 28 | - ./var/docker/redis:/data 29 | -------------------------------------------------------------------------------- /frontend/index.js: -------------------------------------------------------------------------------- 1 | import 'core-js/stable'; 2 | import 'regenerator-runtime/runtime'; 3 | 4 | (ctx => { 5 | return ctx.keys().map(ctx); 6 | })(require.context('./js', true, /\.js$/)); 7 | 8 | (ctx => { 9 | return ctx.keys().map(ctx); 10 | })(require.context('./jsx', true, /\.jsx$/)); 11 | 12 | (ctx => { 13 | return ctx.keys().map(ctx); 14 | })(require.context('./less', true, /\.less$/)); 15 | -------------------------------------------------------------------------------- /frontend/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/frontend/js/.gitkeep -------------------------------------------------------------------------------- /frontend/jsx/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/frontend/jsx/.gitkeep -------------------------------------------------------------------------------- /frontend/less/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/frontend/less/.gitkeep -------------------------------------------------------------------------------- /models/init.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const basename = path.basename(module.filename); 3 | const db = {}; 4 | 5 | const Sequelize = require('sequelize'); 6 | const sequelize = new Sequelize( 7 | config.database.database, 8 | config.database.username, 9 | config.database.password, 10 | config.database 11 | ); 12 | 13 | require('fs') 14 | .readdir_sync(__dirname) 15 | .filter(file => { 16 | return file.indexOf('.') !== 0 && file !== basename && file.match(/\.js$/); 17 | }) 18 | .for_each(file => { 19 | let model = sequelize.import(path.join(__dirname, file)); 20 | db[model.name] = model; 21 | }); 22 | 23 | for (const model_name in db) { 24 | db[model_name].bulk_create = db[model_name].bulkCreate; 25 | db[model_name].find_one = db[model_name].findOne; 26 | db[model_name].find_all = db[model_name].findAll; 27 | db[model_name].find_or_create = db[model_name].findOrCreate; 28 | db[model_name].find_and_count_all = db[model_name].findAndCountAll; 29 | db[model_name].belongs_to = db[model_name].belongsTo; 30 | db[model_name].has_one = db[model_name].hasOne; 31 | db[model_name].has_many = db[model_name].hasMany; 32 | db[model_name].belongs_to_many = db[model_name].belongsToMany; 33 | } 34 | 35 | db.sequelize = sequelize; 36 | db.Sequelize = Sequelize; 37 | 38 | $or = Sequelize.Op.or; 39 | $and = Sequelize.Op.and; 40 | $ne = Sequelize.Op.ne; 41 | $not = Sequelize.Op.not; 42 | 43 | module.exports = db; 44 | -------------------------------------------------------------------------------- /models/posts.js: -------------------------------------------------------------------------------- 1 | const Sequelize = require('sequelize'); 2 | const moment = require('moment'); 3 | 4 | module.exports = (sequelize, DataTypes) => { 5 | class posts extends Sequelize.Model { } 6 | 7 | posts.init( 8 | { 9 | post_id: { 10 | type: DataTypes.INTEGER, 11 | primaryKey: true, 12 | autoIncrement: true 13 | }, 14 | title: DataTypes.STRING, 15 | content: DataTypes.TEXT, 16 | created_at: DataTypes.DATE 17 | }, 18 | { 19 | sequelize, 20 | modelName: 'posts', 21 | freezeTableName: true, 22 | hooks: { 23 | beforeCreate(instance) { 24 | instance.created_at = moment(); 25 | } 26 | } 27 | } 28 | ); 29 | 30 | return posts; 31 | }; 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@babel/core": "7.7.4", 4 | "@babel/plugin-proposal-object-rest-spread": "7.7.4", 5 | "@babel/plugin-transform-async-to-generator": "7.7.4", 6 | "@babel/preset-env": "7.7.4", 7 | "@babel/preset-react": "7.7.4", 8 | "babel-loader": "8.0.6", 9 | "connect-redis": "4.0.4", 10 | "core-js": "3.6.5", 11 | "css-loader": "3.2.1", 12 | "express": "4.17.1", 13 | "express-session": "1.17.1", 14 | "less": "3.10.3", 15 | "less-loader": "5.0.0", 16 | "mini-css-extract-plugin": "0.8.0", 17 | "moment": "2.27.0", 18 | "mysql2": "2.1.0", 19 | "nocamel": "1.0.2", 20 | "nodemon": "2.0.4", 21 | "npm-run-all": "4.1.5", 22 | "react": "16.12.0", 23 | "react-dom": "16.12.0", 24 | "redis": "3.0.2", 25 | "regenerator-runtime": "0.13.3", 26 | "sequelize": "5.21.13", 27 | "style-loader": "1.0.1", 28 | "twig": "1.15.1", 29 | "webpack": "4.41.2", 30 | "webpack-cli": "3.3.10" 31 | }, 32 | "scripts": { 33 | "start": "npm-run-all --parallel proc:webpack proc:app", 34 | "proc:webpack": "webpack --mode development --colors --watch", 35 | "proc:app": "nodemon --ignore frontend/ --ignore public/ -e js,twig app.js" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /public/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/public/.gitkeep -------------------------------------------------------------------------------- /public/images/chase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/public/images/chase.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # simple express.js skeleton application 2 | good for somebody who isn't sure how it all fits together and needs a starting point for making web applications in node.js. 3 | 4 | #### it uses the following key components: 5 | * `express` for handling routes 6 | * `sequelize` as the database orm 7 | * `redis` for session store 8 | * `webpack` for bundling frontend assets 9 | 10 | #### quickstart 11 | ```shell 12 | git clone https://github.com/engineer-man/express-mvc-skeleton 13 | cd express-mvc-skeleton 14 | docker-compose up 15 | ``` 16 | then open http://127.0.0.1:8000 in your browser 17 | 18 | #### other essential commands 19 | ```shell 20 | # access container running your app 21 | docker-compose exec app /bin/bash 22 | 23 | # access mysql 24 | docker-compose exec mysql mysql -uroot -proot 25 | ``` 26 | #### outline of project structure 27 | ``` 28 | |- controllers # these are for handling routes 29 | |- frontend # contains js, jsx, less files to be bundled for the frontend 30 | |- models # these are for accessing your db 31 | |- public # all non-routes get served from here 32 | |- var # variable data for variable purposes 33 | |- views # these are your templates, controllers will render them 34 | ``` 35 | -------------------------------------------------------------------------------- /var/docker/mysql/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/var/docker/mysql/.gitkeep -------------------------------------------------------------------------------- /var/docker/redis/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/engineer-man/express-mvc-skeleton/cd4cfe99eebbaee89a70071dcef99808138945b5/var/docker/redis/.gitkeep -------------------------------------------------------------------------------- /var/sample_schema.sql: -------------------------------------------------------------------------------- 1 | create database if not exists testdb; 2 | 3 | use testdb; 4 | 5 | create table posts ( 6 | post_id int unsigned not null auto_increment, 7 | title varchar(128) not null, 8 | content text not null, 9 | created_at datetime not null, 10 | primary key (post_id) 11 | ) character set utf8mb4 collate utf8mb4_unicode_ci; 12 | 13 | insert into posts values (default, 'First Title', 'First Content', now()); 14 | insert into posts values (default, 'Second Title', 'Second Content', now()); 15 | -------------------------------------------------------------------------------- /views/home/home.twig: -------------------------------------------------------------------------------- 1 | {% extends 'master.twig' %} 2 | {% block content %} 3 | 4 | 5 | {% for post in posts %} 6 |

{{ post.title }}

7 |

8 | {{ post.content }} 9 |

10 | {% endfor %} 11 | {% endblock %} 12 | -------------------------------------------------------------------------------- /views/master.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Title 5 | 6 | 7 | 8 | 9 | {% block content %}{% endblock %} 10 | 11 | 12 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const MiniCSSExtractPlugin = require('mini-css-extract-plugin'); 3 | 4 | module.exports = { 5 | entry: './frontend/index.js', 6 | module: { 7 | rules: [ 8 | { 9 | test: /\.(js|jsx)$/, 10 | exclude: /node_modules/, 11 | use: { 12 | loader: 'babel-loader' 13 | } 14 | }, 15 | { 16 | test: /\.less$/, 17 | exclude: /node_modules/, 18 | use: [ 19 | { 20 | loader: MiniCSSExtractPlugin.loader, 21 | options: { 22 | publicPath: (resourcePath, context) => { 23 | return path.relative(path.dirname(resourcePath), context) + '/'; 24 | } 25 | } 26 | }, 27 | { 28 | loader: 'css-loader' 29 | }, 30 | { 31 | loader: 'less-loader' 32 | } 33 | ] 34 | } 35 | ] 36 | }, 37 | resolve: { 38 | extensions: ['.js', '.jsx', '.json'], 39 | modules: [ 40 | 'node_modules', 41 | path.resolve(__dirname, 'frontend') 42 | ] 43 | }, 44 | output: { 45 | path: path.resolve(__dirname, 'public'), 46 | filename: '[name].bundle.js' 47 | }, 48 | plugins: [ 49 | new MiniCSSExtractPlugin({ 50 | filename: '[name].bundle.css' 51 | }) 52 | ] 53 | }; 54 | --------------------------------------------------------------------------------