π A boilerplate for Node.js, Express, Mongoose, Heroku, mLab, Nodemon, PM2, and Babel.
π Live Demo
This seed repository provides the following features:
- ---------- Essentials ----------
- Application routing with Express.
- Data query language with GraphQL.
- Object document mapping with Mongoose.
- Object relational mapping with Sequelize.
- Utility functions with Lodash.
- Authenticate requests with Passport.
- Real-time bidirectional communication with Socket.IO.
- In-memory data structure store with Redis.
- Jobs and messages with Bull.
- Online payments with Stripe.
- OpenAPI specification with Swagger.
- ---------- Tools ----------
- Next generation JavaScript with Babel.
- JavaScript static code analyzer with ESLint.
- Unit testing with Jest.
- End-to-End testing with Supertest.
- Test coverage integration with Codecov.
- Automatically restart application with Nodemon.
- Keeping application alive with PM2.
- ---------- Environments ----------
- JavaScript runtime with Node.js.
- Fast and deterministic builds with Yarn.
- Version control with Git.
- Code repository with GitHub.
- Cloud application hosting with Heroku.
- Cloud NoSQL database hosting with mLab.
- Cloud SQL database hosting with ElephantSQL.
- Cloud storageβ hosting with Cloudinary.
- Cloud memory cache hosting with RedisLabs.
- API gateway service with WSO2.
- Centralized logging and analysis service with LogDNA.
- Error tracking service with Sentry.
- Performance and security with Cloudflare.
- Software container with Docker.
- Continuous integration with CircleCI.
Thinking in ...
- REST Stack
- GraphQL Stack
- Microservices
Follow steps to execute this boilerplate.
- Clone this boilerplate
$ git clone --depth 1 https://github.com/Shyam-Chen/Express-Starter.git <PROJECT_NAME>
$ cd <PROJECT_NAME>
- Install dependencies
$ yarn install
- Start a local server
$ yarn start
- Compile code
$ yarn build
- Check code quality
$ yarn lint
- Runs unit tests
$ yarn unit
- Runs end-to-end tests
$ yarn e2e
Dockerize an application.
- Build and run the container in the background
$ docker-compose up -d app
- Run a command in a running container
$ docker-compose exec app <COMMAND>
- Remove the old container before creating the new one
$ docker-compose rm -fs
- Restart up the container in the background
$ docker-compose up -d --build app
- Push images to Docker Cloud
# .gitignore
.DS_Store
node_modules
dist
coverage
+ dev.Dockerfile
+ stage.Dockerfile
+ prod.Dockerfile
*.log
$ docker login
$ docker build -f ./tools/<dev|stage|prod>.Dockerfile -t <IMAGE_NAME>:<IMAGE_TAG> .
# checkout
$ docker images
$ docker tag <IMAGE_NAME>:<IMAGE_TAG> <DOCKER_ID_USER>/<IMAGE_NAME>:<IMAGE_TAG>
$ docker push <DOCKER_ID_USER>/<IMAGE_NAME>:<IMAGE_TAG>
# remove
$ docker rmi <REPOSITORY>:<TAG>
# or
$ docker rmi <IMAGE_ID>
- Pull images from Docker Cloud
# circle.yml
echo "${HEROKU_TOKEN}" | docker login -u "${HEROKU_USERNAME}" --password-stdin registry.heroku.com
- docker build -f ./tools/$DEPLOYMENT_ENVIRONMENT.Dockerfile -t $APP_NAME .
+ docker pull <DOCKER_ID_USER>/<IMAGE_NAME>:<IMAGE_TAG>
- docker tag $APP_NAME registry.heroku.com/$APP_NAME/web
+ docker tag <IMAGE_NAME>:<IMAGE_TAG> registry.heroku.com/<HEROKU_PROJECT>/web
docker push registry.heroku.com/<HEROKU_PROJECT>/web
Set your local environment variables.
// src/env.js
export const NODE_ENV = process.env.NODE_ENV || 'development';
export const HOST = process.env.HOST || '0.0.0.0';
export const PORT = process.env.PORT || 3000;
export const SECRET = process.env.SECRET || 'PUT_YOUR_SECRET_HERE';
export const MONGODB_URI = process.env.MONGODB_URI || '<PUT_YOUR_MONGODB_URI_HERE>';
export const POSTGRES_URL = process.env.POSTGRES_URL || 'PUT_YOUR_POSTGRES_URL_HERE';
export const REDIS_PORT = process.env.REDIS_PORT || '<PUT_YOUR_REDIS_PORT_HERE>';
export const REDIS_HOST = process.env.REDIS_HOST || '<PUT_YOUR_REDIS_HOST_HERE>';
// ...
Set your deployment environment variables.
# tools/<dev|stage|prod>.Dockerfile
# envs --
ENV SECRET <PUT_YOUR_SECRET_HERE>
ENV MONGODB_URI <PUT_YOUR_MONGODB_URI>
ENV POSTGRES_URL <PUT_YOUR_POSTGRES_URL_HERE>
ENV REDIS_PORT <PUT_YOUR_REDIS_PORT_HERE>
ENV REDIS_HOST <PUT_YOUR_REDIS_HOST_HERE>
# ...
# -- envs
- Example of REST
import { Router } from 'express';
import { List } from './document';
const router = Router();
router.get('/', async (req, res) => {
const data = await List.find({}).exec();
res.json(data);
});
export default router;
- Example of GraphQL
import gql from 'graphql-tag';
import { List } from './document';
export const listTypeDefs = gql`
type List {
_id: ID!
text: String!
}
type Query {
list: [List]
}
`;
export const listResolvers = {
Query: {
async list(root, { _id, text }) {
const data = await List.find({}).exec();
return data;
},
},
};
- Example of Document
import mongoose, { Schema } from 'mongoose';
const listSchema = new Schema({
text: {
type: String,
required: true,
},
});
export const List = mongoose.model('List', listSchema);
- Example of Relational
import Sequelize from 'sequelize';
import sequelize from '~/core/sequelize';
export const RelationalList = sequelize.define('List', {
text: Sequelize.STRING,
});
- Example of Lodash
import { of } from 'rxjs';
import { lowerFirst, pad } from 'lodash';
of(lowerFirst('Hello'), pad('World', 5))
.subscribe(value => console.log(value));
// hello
// World
- Example of ReactiveX
import { timer, of } from 'rxjs';
import { mapTo, combineAll } from 'rxjs/operators';
timer(2000)
.pipe(
mapTo(of('Hello', 'World')),
combineAll(),
)
.subscribe(value => console.log(value));
// ["Hello"]
// ["World"]
- Example of Socket
connSocket.emit('A', { foo: 'bar' });
connSocket.on('B', data => console.log(data)); // { foo: 'baz' }
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
<script>
const socket = io('http://localhost:3000/');
socket.on('connect', () => console.log('Socket: Accept a connection.'));
socket.on('A', (data) => {
console.log(data); // { foo: 'bar' }
socket.emit('B', { foo: 'baz' });
});
</script>
- Example of Redis
import redis from '~/core/redis';
redis.hmset('thing', {
foo: 'js',
bar: 'html',
baz: 'css',
});
redis.hgetall('thing', (err, object) => {
console.log(object);
});
The structure follows the LIFT Guidelines.
.
βββ src
β βββ core -> core feature module
β βββ <FEATURE> -> feature modules
β β βββ __tests__
β β β βββ <FEATURE>.e2e-spec.js
β β β βββ <FEATURE>.spec.js
β β βββ _<THING> -> feature of private things
β β β βββ ...
β β βββ <FEATURE>.js
β βββ <GROUP> -> module group
β β βββ <FEATURE> -> feature modules
β β βββ __tests__
β β β βββ <FEATURE>.e2e-spec.js
β β β βββ <FEATURE>.spec.js
β β βββ _<THING> -> feature of private things
β β β βββ ...
β β βββ <FEATURE>.js
β βββ shared -> shared feature module
β βββ app.js
β βββ env.js
β βββ server.js
βββ tools
β βββ ...
βββ .editorconfig
βββ .eslintrc
βββ .gitignore
βββ .prettierrc
βββ babel.config
βββ circle.yml
βββ docker-compose.yml
βββ Dockerfile
βββ jest.config.js
βββ LICENSE
βββ package.json
βββ processes.js
βββ README.md
βββ yarn.lock