This time, we will use Sequelize, which is an ORM of Node.js, to access MySQL from the Express server, acquire data, and respond.
Since the creation of the Express server and simple API has been completed by the previous article, this time I will focus on the introduction of Sequelize and the creation of Model.
How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ Vue edition ~ How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ MySQL edition ~ How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ Express edition ~
docker-compose.yml
version: "3"
services:
  app:
    container_name: app_container
    build: ./docker/app
    ports:
      - 8080:8080
    volumes:
      - ./app:/app
    stdin_open: true
    tty: true
    environment:
      TZ: Asia/Tokyo
    command: yarn serve
    networks:                          #Add network
      - default
  api:
    container_name: api_container
    build: ./docker/api
    ports:
      - 3000:3000
    volumes:
      - ./api:/api
    tty: true
    environment:
      CHOKIDAR_USEPOLLING: 1
      TZ: Asia/Tokyo
    depends_on:
      - db
    command: yarn nodemon
    networks:                          #Add network
      - default
  db:
    container_name: db_container
    build: ./docker/db
    image: mysql:5.7
    ports:
      - 3306:3306
    volumes:
      - ./db/conf/my.cnf:/etc/mysql/conf.d/mysql.cnf
      - ./db/init_db:/docker-entrypoint-initdb.d
      - test_data:/var/lib/mysql
    environment:
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - TZ="Asia/Tokyo"
    networks:                          #Add network
      - default
networks:                              #Add network
  default:
volumes:
  test_data:
By connecting with a common network, it is possible to communicate between each component.
$ docker-compose up -d
From here, we will introduce Sequelize, which is an ORM for Node.js (which acquires and operates data from a relational database), and acquires data from the DB.
$ docker exec -it api_container sh
$ yarn add [email protected] sequelize-typescript reflect-metadata mysql2 log4js cors
$ yarn add --dev dotenv @types/validator @types/bluebird @types/cors @types/dotenv
* An error may occur depending on the version of sequelize, so you need to check the version at the time of installation! </ font>
api/index.ts
import cors from 'cors' //add to
import express from 'express'
import router from './routes/index'
const app = express()
const port = 3000
app.use(cors()) //add to
app.use('/api', router)
app.listen(port, () => console.log(`Example app listening on port ${port}!`))
api/models/settings/.env
MYSQL_DATABASE=test_db
MYSQL_USER={What was set when creating the MySQL container}
MYSQL_PASSWORD={What was set when creating the MySQL container}
MYSQL_ROOT_PASSWORD={What was set when creating the MySQL container}
This is the same setting as .env in the root directory.
api/models/settings/db_setting.ts
import dotenv from 'dotenv'
dotenv.config({ path: __dirname + '/.env' })
interface DatabaseTypes {
  database: string | undefined
  user: string | undefined
  password: string | undefined
}
export const dbSetting: DatabaseTypes = {
  database: process.env.MYSQL_DATABASE,
  user: process.env.MYSQL_USER,
  password: process.env.MYSQL_PASSWORD,
}
Set the password and table for Sequelize.
api/models/test.ts
import {
  Table,
  Model,
  Column,
  DataType,
  PrimaryKey,
  AutoIncrement,
} from 'sequelize-typescript'
@Table({
  modelName: 'test',
  tableName: 'test',
})
export class Test extends Model<Test> {
  @PrimaryKey
  @AutoIncrement
  @Column(DataType.INTEGER)
  readonly id!: number
  @Column(DataType.STRING)
  name!: string
  @Column(DataType.STRING)
  description!: string
}
Create a model for the test table. Described on a decorator basis.
I get a TypeScript error, so I added a rule.
api/tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["./**/*"]
}
Edit tsconfig settings above
api/models/index.ts
import log4js from 'log4js'
import { Sequelize } from 'sequelize-typescript'
import { dbSetting } from './settings/db_setting'
import { Test } from './test'
const logger = log4js.getLogger('mysql')
export default new Sequelize({
  dialect: 'mysql',
  timezone: '+09:00',
  port: 3306,
  host: 'db',
  username: dbSetting['user'],
  password: dbSetting['password'],
  database: dbSetting['database'],
  logging: (sql: string) => {
    logger.info(sql)
  },
  define: { timestamps: false, underscored: true },
  pool: { max: 5, min: 0, idle: 10000, acquire: 30000 },
  models: [Test],
})
export { Test }
This is the liver of this time. If you get an error, it should be here. If the library version is different or the tsconfig settings are not correct, an error will occur here.
api/routes/tests/get_tests.ts
import { Request, Response } from 'express'
import { Handler } from '../../core/handler'
import { NO_DATA_EXISTS } from '../../constants/error'
import { Test } from '../../models/index'
export class GetTests {
  handler: Handler
  constructor(req: Request, res: Response) {
    this.handler = new Handler(req, res)
  }
  /**
   *Main processing
   */
  async main() {
    const data = await this.getTests()
    if (!data) {
      return this.handler.error(NO_DATA_EXISTS)
    }
    return this.handler.json(data)
  }
  /**
   *Get all Test data
   */
  getTests() {
    return Test.findAll({
      attributes: ['id', 'name', 'description'],
    })
  }
}
As a processing flow
Go to localhost: 3000 / api / tests!
It's OK if the actual data is returned in JSON!
api/routes/tests/get_test_by_id.ts
import { Request, Response } from 'express'
import { Handler } from '../../core/handler'
import { PARAMETER_INVALID, NO_DATA_EXISTS } from '../../constants/error'
import { Test } from '../../models/index'
type Params = {
  test_id: number
}
export class GetTestById {
  handler: Handler
  params: Params
  constructor(req: Request, res: Response) {
    this.handler = new Handler(req, res)
    this.params = {
      test_id: Number(req.params.test_id),
    }
  }
  /**
   *Main processing
   */
  async main() {
    if (!this.params.test_id) {
      return this.handler.error(PARAMETER_INVALID)
    }
    const data = await this.getTest()
    if (!data) {
      return this.handler.error(NO_DATA_EXISTS)
    }
    return this.handler.json(data)
  }
  /**
   *Get all Test data
   */
  getTest() {
    return Test.findOne({
      attributes: ['id', 'name', 'description'],
      where: {
        id: this.params.test_id,
      },
    })
  }
}
This time, in the findOne function (get one corresponding data), search by the where clause to get the data with the corresponding ID.
testsController.ts
import { Router } from 'express'
import { GetTests } from '../tests/get_tests'
import { GetTestById } from '../tests/get_test_by_id' //add to
const router = Router()
router.get('/', (req, res, next) => {
  new GetTests(req, res).main().catch(next)
})
router.get('/:test_id', (req, res, next) => {   //add to
  new GetTestById(req, res).main().catch(next)  //add to
})                                              //add to
export default router
Pass the ID to be searched to the GetTestById class as a URL query.
Access localhost: 3000 / api / tests / 1

If the data of / tests / ** {ID specified here} ** is returned, it is successful! Please check with other IDs as a trial.
At this point, the server side is complete. From here, we will make settings to actually call the created API from the front side.
$ exit
$ docker exec -it app_container sh
$ yarn add axios
app/src/utils/axios.ts
import axios from 'axios'
export const api = axios.create({
  baseURL: 'http://localhost:3000/api',
})
Define the module for both cors countermeasures and default URL conversion. When calling, it will be possible to call with api.get or api.post.
app/src/store/modules/test.ts
import {
  getModule,
  Module,
  VuexModule,
  Mutation,
  Action,
} from 'vuex-module-decorators'
import store from '../index'
import { api } from '../../utils/axios'
type TestType = {
  id: number
  name: string
  description: string
}
type TestState = {
  apiTests: TestType[]
}
@Module({ store, dynamic: true, namespaced: true, name: 'Test' })
class TestModule extends VuexModule implements TestState {
  apiTests: TestType[] = []
  @Mutation
  SET_API_TESTS(payload: TestType[]) {
    this.apiTests = payload
  }
  @Action
  async getTests() {
    const response = await api.get('/tests')
    if (response.data.data) {
      this.SET_API_TESTS(response.data.data)
    }
  }
}
export const testModule = getModule(TestModule)
Create a Vuex test Store and store the data obtained by calling the API created in GetTests of Action in the apiTests state.
app/src/pages/Test.vue
<template>
  <div class="test">
    <v-data-table :headers="headers" :items="tests"> </v-data-table>
  </div>
</template>
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator'
import { testModule } from '../store/modules/test'
@Component
export default class Test extends Vue {
  tests = []
  headers = [
    { text: 'ID', align: 'center', value: 'id' },
    { text: 'name', align: 'center', value: 'name' },
    { text: 'Details', align: 'center', value: 'description' },
  ]
  async created() {
    await testModule.getTests()
    this.tests = testModule.apiTests
  }
}
</script>
When Vue is created, run the getTests action in the test store to get the data, and bind the apiTests data to reference the data in Vue. Pass that data to Vuetify's v-table-table and you're done.
localhost:8080

So far, I was able to call the API on the front side, get the data from the DB, store the returned data in Vuex, and render it. Now that you have a base, you can extend it by adding APIs and pages in the same way.
Now that the template is complete, I'll create an app that allows you to perform simple CLUD operations next time!
How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ Vue edition ~ How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ MySQL edition ~ How to build [TypeScript + Vue + Express + MySQL] environment with Docker ~ Express edition ~
Recommended Posts