RomainFallet / fastify-starter

Create and configure a new Fastify app from scratch with appropriate linters, editor config, testing utilities and continuous integration (Prettier, ESLint, StandardJS, Markdownlint, Jest, MongoDB, GitHub Actions).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Fastify starter

The purpose of this repository is to provide instructions to create and configure a new Fastify app from scratch with appropriate linters, editor config, testing utilities, continuous integration.

Table of contents

Prerequisites

  • Git v2
  • NodeJS v12
  • NPM v6
  • MongoDB v4.2

Quickstart

# Clone repo
git clone https://github.com/RomainFallet/fastify-starter

# Go inside the project
cd ./fastify-starter

# Install dependencies
npm install

# Create database (replace <dbname>)
mongo --eval "db = db.getSiblingDB('<dbname>')"

# Create a user and grant him access to the db
# (replace <username>, <password> and <dbname>)
mongo --eval "db.createUser(
  {
    user: '<username>',
    pwd:  '<password>',
    roles: [ { role: 'readWrite', db: '<dbname>' } ]
  }
)"

# Load fixtures (replace <dbname>)
mongo <dbname> --eval "$(cat ./fixtures/*)"

Then, copy the ./.env file to ./.env.local and replace variables:

MONGODB_URI=mongodb://<username>:<password>@localhost:27017/<dbname>

Manual configuration

Init the project

Back to top ↑

Create a new ./.npmrc file:

save-prefix='~'

Create a new ./package.json file:

{
  "name": "fastify-starter",
  "version": "1.0.0",
  "license": "UNLICENSED",
  "repository": "git@github.com:RomainFallet/fastify-starter.git",
  "scripts": {
    "start": "nodemon ./src/index.js",
    "test": "is-ci test:all test:watch",
    "test:watch": "jest --watch",
    "test:all": "jest --passWithNoTests",
    "deps:check": "npm-check",
    "deps:upgrade": "npm-check -u",
    "lint": "npm-run-all lint:*",
    "lint:js": "eslint \"./**/*.js\"",
    "lint:json": "prettier --check \"./**/*.json\"",
    "lint:md": "markdownlint \"./**/*.md\" --ignore ./node_modules",
    "lint:yml": "prettier --check \"./**/*.yml\"",
    "format": "npm-run-all format:*",
    "format:json": "prettier --write \"./**/*.json\"",
    "format:js": "eslint --fix \"./**/*.js\"",
    "format:md": "markdownlint --fix \"./**/*.md\" --ignore ./node_modules",
    "format:yml": "prettier --write \"./**/*.yml\""
  },
  "lint-staged": {
    "./**/*.json": [
      "prettier --check"
    ],
    "./**/*.js": [
      "eslint"
    ],
    "./**/*.yml": [
      "prettier --check"
    ],
    "./**/*.md": [
      "markdownlint --ignore ./node_modules"
    ]
  },
  "husky": {
    "hooks": {
      "pre-commit": "lint-staged"
    }
  }
}

Install packages:

# Dependencies
npm install fastify@~2.13.0 axios@~0.19.0 mongodb@~3.5.0 mongoose@~5.9.0 dotenv-flow@~3.1.0

# Dev dependencies
npm install --save-dev nodemon@~2.0.0 npm-run-all@~4.1.5 is-ci-cli@~2.0.0

Create default app

Back to top ↑

Create a new ./src/index.js file:

require('dotenv-flow').config()
const mongoose = require('mongoose')
const app = require('fastify')({
  logger: true
})

const start = async () => {
  try {
    // Connect to the database
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      serverSelectionTimeoutMS: 1000
    })

    // Register routes
    app.register(require('./routes/cats'))

    // Start the webserver
    await app.listen(3000)

    app.log.info(`server listening on ${app.server.address().port}`)
  } catch (err) {
    app.log.error(err)
    process.exit(1)
  }
}

start()

Create a new file ./src/routes/cats.js:

const Cat = require('../models/cat')

module.exports = async app => {
  app.get('/cats', async () => {
    const cats = await Cat.find({})
    return cats.map(cat => cat.toJSON())
  })

  app.post('/cats', async (req, res) => {
    await Cat.create(req.body)
    res.status(204)
  })
}

Create a new file ./src/models/cat.js:

const mongoose = require('mongoose')

const schema = new mongoose.Schema(
  {
    name: String,
    color: String
  },
  { timestamps: true }
)

module.exports = mongoose.model('Cat', schema)

Create a new file ./.env:

MONGODB_URI=mongodb://<username>:<password>@localhost:27017/<database>

Create a new ./fixtures/cat.js file:

/* eslint-env mongo */
// eslint-disable-next-line no-global-assign
ObjectId =
  typeof ObjectId !== 'undefined'
    ? ObjectId
    : require('mongoose').Types.ObjectId

const cat = {
  _id: ObjectId('5e980b9bafdcc9eda8df1ffc'),
  __v: 0,
  name: 'Kitty',
  color: 'brown',
  createdAt: new Date('2020-04-16T08:57:33.198Z'),
  updatedAt: new Date('2020-04-16T08:57:33.198Z')
}

if (typeof db !== 'undefined') {
  db.cats.remove({})
  db.cats.insert(cat)
}

module.exports = cat

Install Jest & testing utilities

Back to top ↑

Install packages:

npm install --save-dev jest@~25.2.0 axios-mock-adapter@~1.18.0 mongodb-memory-server@~6.5.0

Create a new ./jest.config.js file:

module.exports = {
  testEnvironment: "node",
}

Create a new ./src/helpers/test-utils.js:

const { MongoMemoryServer } = require('mongodb-memory-server')
const mongoose = require('mongoose')

const setupMongo = async () => {
  const mongoServer = new MongoMemoryServer({ autoStart: false })
  const connection = await mongoose.connect(await mongoServer.getUri(), {
    useNewUrlParser: true,
    useUnifiedTopology: true,
    serverSelectionTimeoutMS: 100
  })
  return { mongoServer, connection }
}
const cleanMongo = async ({ mongoServer, connection }) => {
  await mongoServer.stop()
  await connection.disconnect()
}

module.exports = { setupMongo, cleanMongo }

Create a new ./src/routes/cat.test.js file:

const fastify = require('fastify')
const { setupMongo, cleanMongo } = require('../helpers/test-utils')
const Cat = require('../models/cat')
const mongoose = require('mongoose')
const catsRoute = require('./cats')
const cat = require('./../../fixtures/cat')

describe('/cats', () => {
  describe('GET /cats', () => {
    it('responds 200 and return cats', async () => {
      // Arrange
      expect.assertions(2)
      const app = fastify().register(catsRoute)
      const { mongoServer, connection } = await setupMongo()
      await Cat.create(cat)

      // Act
      const res = await app.inject({
        method: 'GET',
        url: '/cats'
      })

      // Clean up
      await cleanMongo({ mongoServer, connection })

      // Assert
      expect(res.statusCode).toBe(200)
      expect(res.json()).toStrictEqual([
        {
          _id: cat._id.toString(),
          __v: cat.__v,
          name: cat.name,
          color: cat.color,
          createdAt: cat.createdAt.toISOString(),
          updatedAt: cat.updatedAt.toISOString()
        }
      ])
    })
  })

  describe('POST /cats', () => {
    it('responds 204 and save cat', async () => {
      // Arrange
      expect.assertions(3)
      const app = fastify().register(catsRoute)
      const { mongoServer, connection } = await setupMongo()

      // Act
      const res = await app.inject({
        method: 'POST',
        url: '/cats',
        body: { name: 'Meow', color: 'dark' }
      })
      const savedCat = (await Cat.findOne({ name: 'Meow' })).toObject()

      // Clean up
      await cleanMongo({ mongoServer, connection })

      // Assert
      expect(res.statusCode).toBe(204)
      expect(res.body).toBe('')
      expect(savedCat).toStrictEqual({
        _id: expect.any(mongoose.Types.ObjectId),
        __v: 0,
        color: 'dark',
        name: 'Meow',
        createdAt: expect.any(Date),
        updatedAt: expect.any(Date)
      })
    })
  })
})

Install Prettier code formatter

Back to top ↑

# Install Prettier with StandardJS config
npm i -D prettier@~2.0.0 prettier-config-standard@~1.0.0

# Install configs for ESLint integration
npm i -D eslint-plugin-prettier@~3.1.0 eslint-config-prettier@~6.10.0 eslint-config-prettier-standard@~3.0.0

Install ESLint code linter with StandardJS rules

Back to top ↑

# Install ESLint
npm i -D eslint@~6.8.0

# Install ESLint default plugins
npm i -D eslint-plugin-promise@~4.2.0 eslint-plugin-import@~2.20.0 eslint-plugin-node@~11.1.0

# Install StandardJS & Jest plugins
npm i -D eslint-plugin-standard@~4.0.0 eslint-plugin-jest@~23.8.0

# Install StandardJS config
npm i -D eslint-config-standard@~14.1.0

Create a new ./.eslintrc.json file:

{
  "extends": ["standard", "prettier-standard"],
  "overrides": [
    {
      "files": ["./src/**/*.test.js"],
      "extends": "plugin:jest/all"
    }
  ],
  "plugins": ["jest"]
}

Install MarkdownLint code linter

Back to top ↑

npm install --save-dev markdownlint@~0.19.0 markdownlint-cli@~0.22.0

Create a new ./.markdownlint.json file:

{
  "default": true
}

Install dependencies checker

Back to top ↑

npm install --save-dev npm-check@~5.9.0

Configure .gitignore

Back to top ↑

Create a new ./.gitignore file:

# OS Specific
.DS_Store

# Dependencies
node_modules

# Environment
.env.local

Configure .editorconfig

Back to top ↑

Create a new ./.editorconfig file:

# EditorConfig is awesome: https://EditorConfig.org
root = true

[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

Configure CI with Git hooks

Back to top ↑

npm install --save-dev husky@~4.2.0 lint-staged@~10.1.0

Configure CI with GitHub Actions

Back to top ↑

Create a new ./.github/workflows/lint.yml file:

name: Check coding style and lint code

on: ["push", "pull_request"]

jobs:
  lint:
    runs-on: ubuntu-18.04

    steps:
      - uses: actions/checkout@v2
      - name: Cache node modules
        uses: actions/cache@v1
        env:
          cache-name: cache-node-modules
        with:
          path: ~/.npm
          key: ${{ env.cache-name }}-${{ hashFiles('./package-lock.json') }}
          restore-keys: ${{ env.cache-name }}-
      - name: Install dependencies
        run: npm install
      - name: Check coding style and lint code
        run: npm run lint

Create a new ./.github/workflows/test.yml file:

name: Launch unit tests

on: ["pull_request"]

jobs:
  test:
    runs-on: ubuntu-18.04

    steps:
      - uses: actions/checkout@v2
      - name: Cache node modules
        uses: actions/cache@v1
        env:
          cache-name: cache-node-modules
        with:
          path: ~/.npm
          key: ${{ env.cache-name }}-${{ hashFiles('./package-lock.json') }}
          restore-keys: ${{ env.cache-name }}-
      - name: Install dependencies
        run: npm install
      - name: Launch test with Jest
        run: npm test

Integrate formatters, linters & syntax to VSCode

Back to top ↑

Create a new ./.vscode/extensions.json file:

{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "davidanson.vscode-markdownlint",
    "me-dutour-mathieu.vscode-github-actions",
    "mikestead.dotenv",
    "editorconfig.editorconfig",
    "eg2.vscode-npm-script"
  ]
}

This will suggest to install npm, Prettier, ESLint, MarkdownLint, Github Actions, DotENV and EditorConfig extensions to everybody opening this project in VSCode.

Then, create a new ./.vscode/settings.json file:

{
  "eslint.enable": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
    "source.fixAll.markdownlint": true
  },
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "prettier.disableLanguages": ["javascript", "markdown"]
}

This will format automatically the code on save.

Usage

Launch app

Back to top ↑

npm start

Launch unit tests

Back to top ↑

# Test only changes since last commit in watch mode
npm test

# Run all test suite
npm run test:all

Check coding style & Lint code for errors/bad practices

Back to top ↑

# Check all files
npm run lint

# Check JavaScript with ESLint (Prettier + StandardJS)
npm run lint:js

# Check JSON with Prettier
npm run lint:json

# Check YAML with Prettier
npm run lint:yml

# Check Mardkown with MarkdownLint
npm run lint:md

Format code automatically

Back to top ↑

# Format all files
npm run format

# Format JavaScript with ESLint (Prettier + StandardJS)
npm run format:js

# Format JSON with Prettier
npm run format:json

# Format YAML with Prettier
npm run format:yml

# Format Mardkown with MarkdownLint
npm run format:md

Audit & fix dependencies vulnerabilities

Back to top ↑

# Check for known vulnerabilities in dependencies
npm audit

# Install latest patches of all dependencies
npm update

Check & upgrade outdated dependencies

Back to top ↑

# Check for unused/outdated dependencies
npm run deps:check

# Choose interactively which dependency to upgrade
npm run deps:upgrade

About

Create and configure a new Fastify app from scratch with appropriate linters, editor config, testing utilities and continuous integration (Prettier, ESLint, StandardJS, Markdownlint, Jest, MongoDB, GitHub Actions).


Languages

Language:JavaScript 100.0%