참고: NodeJS Express Typescript로 Sequelize환경구축
위의 블로그 글을 참고하면서 작성했다.
그런데 나는 migrations 이용해서 하는 게 불편해서(그리 큰 규모의 프로젝트도 아니라,,) 그냥 models 디렉토리 안에 모델을 짜고 app.ts에서 sequelize를 sync하는 방식을 채택했다.
그간 담다 프로젝트를 진행하면서 서버를 nodejs + sequelize + js 로 구성했었는데, typescript까지 제대로 다뤄보고 싶어서 공씨 프로젝트를 typescript로 진행하게 되었다.
ts를 각 잡고 공부하진 않았지만 타입을 지정해줘야 한다는 것만 익숙해지니까 그렇게 어렵지는 않았다.
Nest.js 도 있지만 프로젝트 완성까지 남은 시간을 계산해 보고 Nest는 아쉽게도 시도하지 않았다.
언젠간 Nest도 다뤄보고 싶다.
"dependencies": {
"cors": "2.8.5",
"dotenv": "10.0.0",
"express": "4.17.1",
"mysql2": "2.3.3",
"reflect-metadata": "0.1.13",
"sequelize": "6.9.0",
"sequelize-cli": "6.3.0",
"sequelize-typescript": "2.1.1"
},
"devDependencies": {
"@types/cors": "2.8.12",
"@types/express": "4.17.13",
"@types/node": "16.10.3",
"@types/validator": "13.6.6",
"nodemon": "2.0.15",
"ts-node": "10.2.1",
"tsc-watch": "4.5.0",
"typescript": "4.4.3"
}
src/app.ts
라우터를 아직 설정하지 않은 상태다.
// src/app.ts
import * as dotenv from "dotenv";
import express, { Response, Request, NextFunction } from "express";
import cors from "cors";
import { sequelize } from "./models";
import User from "./models/user.model";
dotenv.config();
// * APP VARIABLES
const PORT: number = parseInt(process.env.PORT as string, 10) || 5000;
const HOST: string = process.env.HOST || "localhost";
const app: express.Application = express();
// * APP CONFIGURATION: middleware
app.use(cors());
app.use(express.json());
app.use((req: Request, res: Response, next: NextFunction) => {
console.log(`Request occur! ${req.method}, ${req.url}`);
next();
});
// TODO ROUTER SETTING
// 5000 포트로 서버 실행
app.listen(PORT, HOST, async () => {
console.log(`server on: listening on ${HOST}:${PORT}`);
// sequelize-db connection test
await sequelize
.sync({ force: true })
.then(async () => {
console.log("seq connection success");
})
.catch((e) => {
console.log("seq ERROR: ", e);
});
});
src/config/config.ts
dotenv를 사용해서 비밀... 들을 관리해줬다.
import * as dotenv from "dotenv";
dotenv.config();
const env = process.env;
export const config = {
development: {
username: env.MYSQL_USERNAME || "root",
password: env.MYSQL_PASSWORD,
database: env.MYSQL_DATABASE || "gongcdb",
host: env.MYSQL_HOST || "localhost",
dialect: "mysql",
port: env.MYSQL_PORT || "3306",
},
};
tsconfig.json
이거 안하면 에러 난다.
// ...생략...
"experimentalDecorators": true
// ...생략...
여기서 에러가 많이 났다.
import {
Table,
Column,
Model,
AllowNull,
Unique,
DataType,
} from "sequelize-typescript";
@Table({ timestamps: false })
export default class User extends Model {
// id는 자동으로 auto_increment, primarykey 설정된 채로 추가됨.
@AllowNull(false)
@Unique(true)
@Column(DataType.STRING)
public email!: string;
@AllowNull(false)
@Column(DataType.STRING)
public password!: string;
@AllowNull(false)
@Column(DataType.STRING)
public nickname!: string;
@Unique(true)
@AllowNull(true)
@Column(DataType.STRING)
public rasp_token!: string | null;
@Unique(true)
@AllowNull(true)
@Column(DataType.STRING)
public android_token!: string | null;
}
이때, 제일 중요한 건 @Column 데코레이터를 제일 아래에 둬야 한다는 것!
아래와 같이 @Column 데코레이터를 가장 아래에 안 두고 아무데나 두면 에러가 뜸.
export default class User extends Model {
@Column(DataType.STRING)
@AllowNull(false)
@Unique(true)
public email!: string;
// ... 생략
}
// 에러 메시지
// throw new Error(`@Column annotation is missing for "${propertyName}" of class "${target.constructor.name}"` +
// ^
// Error: @Column annotation is missing for "email" of class "User" or annotation order is wrong.
참고: https://github.com/RobinBuschmann/sequelize-typescript/issues/692
import { Sequelize } from "sequelize-typescript";
import { config } from "../config/config";
export const sequelize = new Sequelize(
config.development.database,
config.development.username,
config.development.password,
{
host: config.development.host,
dialect: "mysql",
models: [__dirname + "/**/*.model.ts"],
}
);
// src/app.ts
import * as dotenv from "dotenv";
import express, { Response, Request, NextFunction } from "express";
import cors from "cors";
import { sequelize } from "./models";
import User from "./models/user.model";
dotenv.config();
// * APP VARIABLES
const PORT: number = parseInt(process.env.PORT as string, 10) || 5000;
const HOST: string = process.env.HOST || "localhost";
const app: express.Application = express();
// * APP CONFIGURATION: middleware
app.use(cors());
app.use(express.json());
app.use((req: Request, res: Response, next: NextFunction) => {
console.log(`Request occur! ${req.method}, ${req.url}`);
next();
});
// TODO ROUTER SETTING
// get
app.get("/", (req: Request, res: Response) => {
res.send("hello express");
});
app.get("/test", (req: Request, res: Response) => {
// email password nickname rasp_token android_token
const user = new User({
email: "test@gmail.com",
password: "1111",
nickname: "안녕",
});
user.save();
// 이거 안 쓰면 저장 안됨!
res.status(200).send({ done: true });
// res.status(400).send({ done: false });
});
// 5000 포트로 서버 실행
app.listen(PORT, HOST, async () => {
console.log(`server on: listening on ${HOST}:${PORT}`);
// sequelize-db connection test
await sequelize
.sync({ force: true })
.then(async () => {
console.log("seq connection success");
})
.catch((e) => {
console.log("seq ERROR: ", e);
});
});
그런데 또 에러가 뜬다.
에러: TypeError: Class constructor model cannot be invoked without 'new'
그래서 위 스택오버플로우 답변처럼 tsconfig.json으로 가서 설정을 아래와 같이 바꾸면 아주 잘된다.
"target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,