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