-
Notifications
You must be signed in to change notification settings - Fork 3
PoC
- Swagger builder ํจํค์ง ๋น๊ต
- Request validate ํจํค์ง ๋น๊ต
- Raw Query VS Query Builder VS ORM
- ORM ํจํค์ง ๋น๊ต
Package | Stars | Open issues | Close issues | Last commit |
---|---|---|---|---|
tsoa | 2752 | 95 | 816 | 2023.06.23 |
swagger-autogen | 317 | 37 | 153 | 2023.06.25 |
swagger-jsdoc | 1536 | 30 | 150 | 2023.01.16 |
tspec | 42 | 0 | 2 | 2023.06.21 |
์ฅ์
- TypeScript ๊ธฐ๋ฐ.
- ์ฝ๋๋ก swagger ๋ฅผ ๊ด๋ฆฌํ ์ ์์ด ์ค์จ๊ฑฐ ํ์์ ์ผ์ ํ๊ฒ ์ ์ง๊ฐ๋ฅ. (SSOT)
- ํ ์คํธ ํ์์ด ํ์ํ ๊ฒฝ์ฐ ์ฃผ์์ผ๋ก ์์ฑ ๊ฐ๋ฅ.
- ์์ธํ ์์ ์ฝ๋๋ฅผ ํ๋ถํ๊ฒ ์ ๊ณต.
๋จ์
- TypeScript ์ ๊ด๋ จ ์ค์ ์ ์ถ๊ฐํด์ค์ผ ๋จ.
- TypeScript ์ decorator ๋ฅผ ์ฌ์ฉํ์ฌ ๊ธฐ๋ฅ์ด ๊ตฌํ๋์ด ์์.(ํ์ฌ decorator ๋ experimental ํ ๊ธฐ๋ฅ)
- Controller ์ฝ๋๋ฅผ
tsoa
๋ฌธ๋ฒ์ ๋ง๊ฒ ๋ณ๊ฒฝํด์ผ ํจ.
์์ ์ฝ๋
import { Get, Route } from "tsoa";
interface PingResponse {
message: string;
}
@Route("ping")
export default class PingController {
@Get("/")
public async getMessage(): Promise<PingResponse> {
return {
message: "pong",
};
}
}
์ฅ์
- Controller ์ ๊ฒฝ๋ก๋ค๊ณผ ์ฃผ์์ ์ฝ์ด ์๋์ผ๋ก ํตํฉ๋ ํ์ผ์ ์์ฑ.
๋จ์
- ์ฃผ์์ผ๋ก swagger ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์์ ์ ์ผ์ผ์ด ํ์ธํด์ ๋ฐ์ํด์ผ ๋จ.
์์ ์ฝ๋
app.post('/users', (req, res) => {
...
/* #swagger.parameters['obj'] = {
in: 'body',
description: 'Add a user',
schema: { $ref: '#/definitions/AddUser' }
} */
...
})
์ฅ์
- ๋จ์ํ ๊ธฐ๋ฅ์ ์ง์ํ๊ธฐ ๋๋ฌธ์ ์ค์ ์ด ํธํจ.
๋จ์
- YAML ํ์ผ๋ก ์์ฑ๋ ํ์ผ๋ค์ ์ฝ์ด ํ๋์ ํ์ผ๋ก ํตํฉํ๋ ๊ธฐ๋ฅ๋ง์ ํฌํจ.
- ์ฃผ์์ผ๋ก swagger ๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์ ์์ ์ ์ผ์ผ์ด ํ์ธํด์ ๋ฐ์ํด์ผ ๋จ.
์์ ์ฝ๋
/**
* @openapi
* /:
* get:
* description: Welcome to swagger-jsdoc!
* responses:
* 200:
* description: Returns a mysterious string.
*/
app.get('/', (req, res) => {
res.send('Hello World!');
});
์ฅ์
- TypeScript ๊ธฐ๋ฐ.
- ์ฝ๋๋ก swagger ๋ฅผ ๊ด๋ฆฌํ ์ ์์ด ์ค์จ๊ฑฐ ํ์์ ์ผ์ ํ๊ฒ ์ ์ง๊ฐ๋ฅ. (SSOT).
- ์์ธํ ์์ ์ฝ๋๋ฅผ ํ๋ถํ๊ฒ ์ ๊ณต.
๋จ์
- ์ถ์ํ์ง ์ผ๋ง๋์ง ์์ ์์์น ๋ชปํ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์.
- Open API Spec ์ ์ํ ํ์ ๊ณผ handler ํจ์์ ํ์ ๊ฐ ์ฑํฌ๋ฅผ ๋ง์ถฐ์ผ ๋จ.
์์ ์ฝ๋
interface Greeting {
message: string;
}
type GreetingApiSpec = Tspec.DefineApiSpec<{
basePath: '/api/v1/dev';
paths: {
'/greeting': {
post: {
summary: 'Greeting';
requestBody: Greeting;
responses: {
201: Greeting;
};
};
};
};
}>;
Controller ์ ํํ๋ฅผ ์ ํ ์ ํํ์ง ์์ ๊ฐ์ฅ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์๊ณ SSoT ๋ฅผ ์งํฌ ์ ์๋ค๋ ์ฅ์ ์ผ๋ก tspec
์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ . ๋จ์ ์ผ๋ก ๊ผฝ์๋ ์ถ์๋์ง ์ผ๋ง๋์ง ์์ ๋ฌธ์ ๊ฐ ์๊ธธ ์ ์๋ค๋ ์ ์ ๊ณ์ํด์ ์ถ์ด๋ฅผ ์ง์ผ๋ด์ผํ ํ์๊ฐ ์์. ์ด์ธ๋ก Open API Spec ์ ์ํ ํ์
๊ณผ handler ํจ์์ ํ์
๊ฐ ์ฑํฌ๋ฅผ ๋ง์ถฐ์ผ ํ๋ ๋ถ๋ถ์ด ์์ง๋ง ์ฅ์ ์ ๋นํด ํฐ ๋จ์ ์ด๋ผ ํ๋จํ์ง ์์์.
Package | Stars | Open issues | Closed issues | Last commit |
---|---|---|---|---|
class-validator | 9.6k | 204 | 711 | 2023.07.14 |
express-validator | 5.6k | 46 | 813 | 2023.07.15 |
express-openapi-validator | 781 | 131 | 250 | 2023.05.01 |
ํน์ง
- Class ์ decorator ๋ฅผ ์ ์ฉํ์ฌ ๊ฐ propertry ๋ค์ validate ํ๋ ํจํค์ง
์ฅ์
- ๊ฐ ํ๋์ ๋ํด ์ ๋ฐํ validation ๋ด์ฉ๋ค์ ์์ฑํ ์ ์๋ค. (์์ธํ ๋ด์ฉ)
๋จ์
- ๊ธฐ๋ฅ ๊ตฌํ์ด class ๋ฅผ ๋ฒ ์ด์ค๋ก ์ด๋ฃจ์ด์ ธ ์๊ธฐ ๋๋ฌธ์ plain object ์ ์ ์ฉํ๊ธฐ ์ํด์๋ ๋ณ๋์ class instantiate ํ๋ ๊ณผ์ ์ ๊ฑฐ์ณ์ผ ํ๋ค.
- ๊ฐ ํ๋๋ง๋ค ๊ฒ์ฆํ ๋ด์ฉ๋ค์ ๋ฐ์ฝ๋ ์ดํฐ๋ก ์์ฑํด์ผํ๊ธฐ ๋๋ฌธ์ ์์ฑํด์ผ๋ ์ฝ๋๊ฐ ๋ง๋ค.
- 1์ ๋ด์ฉ๋๋ก class instantiate ํ๋ ๊ณผ์ ์ด ํ์์ด๊ธฐ์ target class ๋ฅผ ์์์ผ ๊ฒ์ฆํ ์ ์๋ค. ๋ฐ๋ผ์ ์์ฒญ์ ํ์ ์ ๋ณด๋ฅผ ์๊ณ ์๋ handler ์ ๋งํผ์ validate ํจ์ ํธ์ถ ์ฝ๋ ์ค๋ณต์ด ๋ฐ์ํ ์ ์๋ค.
์์ ์ฝ๋
import {
validate,
validateOrReject,
Contains,
IsInt,
Length,
IsEmail,
IsFQDN,
IsDate,
Min,
Max,
} from 'class-validator';
export class Post {
@Length(10, 20)
title: string;
@Contains('hello')
text: string;
@IsInt()
@Min(0)
@Max(10)
rating: number;
@IsEmail()
email: string;
@IsFQDN()
site: string;
@IsDate()
createDate: Date;
}
let post = new Post();
post.title = 'Hello'; // should not pass
post.text = 'this is a great post about hell world'; // should not pass
post.rating = 11; // should not pass
post.email = 'google.com'; // should not pass
post.site = 'googlecom'; // should not pass
validate(post).then(errors => {
// errors is an array of validation errors
if (errors.length > 0) {
console.log('validation failed. errors: ', errors);
} else {
console.log('validation succeed');
}
});
validateOrReject(post).catch(errors => {
console.log('Promise rejected (validation failed). Errors: ', errors);
});
// or
async function validateOrRejectExample(input) {
try {
await validateOrReject(input);
} catch (errors) {
console.log('Caught promise rejection (validation failed). Errors: ', errors);
}
}
ํน์ง
- ๋ฏธ๋ฆฌ ์ ์๋ express ์ middleware ๋ค์ ์ฌ์ฉํ์ฌ request ๋ฅผ validate ๋ฐ sanitize ํ๋ ํจํค์ง
์ฅ์
- Validation chain ์ ํ์ฉํ์ฌ ๊ฒ์ฆ๊ณผ ๋ด์ฉ์ ์ ์ ๋ฅผ ์ํ๋ ์์๋๋ก ๊ตฌ์ฑํ ์ ์๋ค.
๋จ์
- Validation chain ์ ์ฌ์ฌ์ฉํ ๋ ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ์ ์๋ค.(์ฐธ๊ณ )
- 1์ ๋ฒ๊ทธ๋ฅผ ๋ง๊ธฐ ์ํด์๋ ๋งค๋ฒ ๊ฐ์ ๊ฒ์ฆ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๋ค.
์์ ์ฝ๋
import * as express from 'express';
import { query, validationResult } from 'express-validator';
const app = express();
app.use(express.json());
app.get('/hello', query('person').notEmpty(), (req, res) => {
const result = validationResult(req);
if (result.isEmpty()) {
return res.send(`Hello, ${req.query.person}!`);
}
res.send({ errors: result.array() });
});
app.listen(3000);
ํน์ง
- OAS(OpenAPI Specification) ์ ๋ช ์๋ ๋ด์ฉ์ ํตํด์ request ๋ฐ response ๋ฅผ validation ํ๋ ํจํค์ง
์ฅ์
- OAS ์ schema ๋ด์ฉ์ ๋ณด๊ณ validate ํ๊ณ ๋๋ถ๋ถ์ ํ์ ๋ค์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์ํ๊ธฐ ๋๋ฌธ์ ์์ฑํด์ผ๋ ์ฝ๋์ ์์ด ์ ์.(๋ฏธ๋ฆฌ ์ ์๋ middleware ๋ฅผ ์ ์ฉํ๊ธฐ๋ง ํ๋ฉด ๋จ)
- ํ๋ถํ ์๋ฌ ๋ด์ฉ ์ ๊ณต (status code ํฌํจ)
๋จ์
- ํจํค์ง์ ์๋ฌ ํํ๋ฅผ ์๋ฒ์์ ์ดํดํ ์ ์๊ฒ ๊ฐ๊ณตํ๋ ์์ ์ด ํ์ํจ.
- ๋ฌธ์ ๋ด์ฉ์์ ๋ถ์กฑํ ๋ด์ฉ์ด ์ผ๋ถ ๋ณด์.
์์ ์ฝ๋
const express = require('express');
const path = require('path');
const http = require('http');
const app = express();
// 1. Import the express-openapi-validator library
const OpenApiValidator = require('express-openapi-validator');
// 2. Set up body parsers for the request body types you expect
// Must be specified prior to endpoints in 5.
app.use(express.json());
app.use(express.text());
app.use(express.urlencoded({ extended: false }));
// 3. (optionally) Serve the OpenAPI spec
const spec = path.join(__dirname, 'api.yaml');
app.use('/spec', express.static(spec));
// 4. Install the OpenApiValidator onto your express app
app.use(
OpenApiValidator.middleware({
apiSpec: './api.yaml',
validateResponses: true, // <-- to validate responses
}),
);
// 5. Define routes using Express
app.get('/v1/pets', function (req, res, next) {
res.json([
{ id: 1, type: 'cat', name: 'max' },
{ id: 2, type: 'cat', name: 'mini' },
]);
});
// 6. Create an Express error handler
app.use((err, req, res, next) => {
// 7. Customize errors
console.error(err); // dump error to console for debug
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
http.createServer(app).listen(3000);
์ ๋ขฐ์ฑ์ ์ธก๋ฉด์์ star ์ , open / close issue ์ ๋น์จ๋ฑ์ ์์น๋ class-validator
์ express-validator
๊ฐ ์ข์ ๋ฉด์ด ์์. ํ์ง๋ง, ๋งค๋ฒ ๊ฐ์ ๋ด์ฉ์ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๊ณ ํจํค์ง์ ๊ธฐ๋ฅ ๊ตฌํ ๋ฐฉ์์ผ๋ก ์ธํ ๊ณ ์ ์ ์ธ ๊ตฌ์กฐ(i.e validate ๋ฅผ ์ํด์๋ class instance ํ๋ ๊ณผ์ ์ด ํฌํจ๋์ด์ผ ํ๋ค๋ ์ง)๋ ์ฌ์ฉ์ฑ์ ํฐ ์ ์ฝ์ด ์์.
๋ฐ๋ผ์, ํจํค์ง์ ์ ๋ขฐ์ฑ์ ๋จ์ด์ง๋๋ผ๋ ์ฌ์ฉํ๊ธฐ์๋ ๋ณด๋ค ๋ ๊น๋ํ ๊ตฌ์กฐ๋ฅผ ๊ฐ์ง๊ณ ์๋ express-openapi-validator
๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ .
๋ค๋ง, ์ ๋ขฐ์ฑ์ด ๋จ์ด์ง๋ ํจํค์ง์ด๊ธฐ ๋๋ฌธ์ ์ถฉ๋ถํ ํ
์คํธ ๋ฐ repo ์ ์ฌ๋ผ์จ ์ด์ ๋ด์ฉ๋ค์ ํ์ธํ ํ์๊ฐ ์์.
- ์ง์๋ฅผ ์์ฑํ๊ณ ๊ด๋ฆฌํ๋ ๊ฒ์ ๋ช ์์ ์ผ๋ก ํ ์ ์๋ค.
- ์ถ์ํ๋ฅผ ํตํด ๊ตฌํ๋ ๋ค๋ฅธ tool ๋ค๋ณด๋ค ์ฑ๋ฅ์ด ์ข์ ์ ์๋ค.
- ์ ์ฐ์ฑ์ด ์ข๋ค. ์ง์ ์ง์๋ฅผ ์์ฑํ๋๊ฒ ์ ์ฐ์ฑ์ด ๋ค๋ฅธ ์ด๋ค ์ถ์ํ๊ฐ ์ ์ฉ๋ tool ๋ค๋ณด๋ค ์ข์ ์ ์๋ค.
- ๊ฐํ๋ฅธ ๋ฌ๋ ์ปค๋ธ. ์๋ํ๋ ๋๋ก ์ ๋๋ก ๋ค๋ฃจ๊ธฐ ์ํด์๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ตฌ์กฐ์ SQL ์ ๋ํด์ ์์์ผ ํ๋๊ฒ ๋ง๋ค.
- SQL injection ๊ณผ ๊ฐ์ ๋ฌธ์ ๋ฅผ ๋ง๊ธฐ ์ํด ์ ๋ ฅ์ ์ ํํ๋ ๋ก์ง์ ๋ง๋ค์ด ์ ์ฉํด์ผ ํ๋ค. ์ง์ ๊ตฌํํด์ผ ํ๊ธฐ ๋๋ฌธ์ ์๊ฐ๊ณผ ์ค์๋ก ์ธํ ์น๋ช ์ ์ธ ๋ณด์์ ๋ํ ๊ฒฐ์ ์ด ์๊ธธ ์ ์๋ค.
- ์ผ๋ฐ ๋ฌธ์์ด๋ก ๊ด๋ฆฌ๊ฐ ๋๊ธฐ ๋๋ฌธ์ ์ฌ๋ฌ ๊ณ์ธต์ ๋ฐ์ดํฐ๋ค๊ณผ ์์ด๋ฉฐ ์กฐ์ํ๊ธฐ ๋ณต์กํด์ง ์ ์๋ค.
- ๋ฌธ์์ด์ด ์๋ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ๋ฉ์๋๋ ํจ์๋ก ๊ตฌํ๋๊ธฐ ๋๋ฌธ์ raw query ๋ณด๋ค๋ ์ฅ๊ธฐ์ ์ผ๋ก ๊ด๋ฆฌํ๊ธฐ ํธํ๋ค.
- Raw query ๋ณด๋ค๋ ์๋๋๋ผ๋ ์ค์ ๋ง๋ค์ด์ง๋ ์ง์์ ๋ด์ฉ์ด ๊ฝค ํฌ๋ช ํ๋ค. ๊ธฐ๋ฅ์์ผ๋ก ์ค์ ์ง์๋ฅผ ๋ณด์ฌ์ฃผ๋ ๊ฒฝ์ฐ๋ ์๊ณ ๊ทธ๊ฒ ์๋๋๋ผ๋ ์ฐ์ฐ์ด ์ด๋ค ์์ ์ ์ํํ ์ง ์ฝ๊ฒ ์ดํด๊ฐ ๊ฐ๋ฅํ๋ค.
- Query builder ๋ ์ฌ๋ฌ๊ฐ์ง ๊ด๊ณํ DBMS ๋ฅผ ์ถ์ํ๋ฅผ ํตํด ์ง์ํ๊ธฐ ๋๋ฌธ์ ๋ค๋ฅธ DBMS ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ ๋, ๋์์ ๋ฐ์ ์ ์๋ค.
- Raw query ๋ฅผ ์ฌ์ฉํ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก DB ์ ๊ตฌ์กฐ์ ๊ธฐ๋ฅ์ ๋ํด ํ์ตํ๋ ๊ฒ์ด ํ์ํ๋ค. ๊ทธ๋ฟ๋ง์๋๋ผ query builder ๊ฐ ์ด๋ป๊ฒ ๋์ํ ์ง์ ๋ํด์๋ ์ถ๊ฐ์ ์ผ๋ก ํ์ต์ ํด์ผํ๋ค.
- Query builder ๋ DB ์ ๋ฐ์ดํฐ์ ์ดํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ดํฐ๊ฐ ์ด๋ป๊ฒ ๋งคํ๋๋์ง ์ ์ํด์ผ ํ๋ค.
- ์ถ์ํ๋ฅผ ํตํ์ฌ ํ์ฌ์ ์์ ์ ์ฐ์ฅ์์ ๋ฐ์ดํฐ ์์คํ ์ ์ ๊ทผํ๊ณ ์กฐ์ํ๋ ๊ฒ์ ๋์์ ์ค๋ค.
- DB ์์ ๋ฐ์ดํฐ ์ก์์ ์ ํ์ํ ๋ง์ boilerplate ๊ฐ ํ์ ์์ด์ง๊ฒ ๋๋ค. ORM ์๋ migration tool ์ด ํฌํจ๋ ๊ฒฝ์ฐ๋ ์๊ธฐ ๋๋ฌธ์, ๊ฐ๋ฐ์๊ฐ schema ๋ณ๊ฒฝ์ ์ ์๋ฒฝํ ์ฐพ์ DB ์ ์ ์ฉํ ํ์์์ด DB ์ ๊ตฌ์กฐ ๋ณ๊ฒฝ์ ๋์์ ์ค๋ค.
- ๋์ ์ ๋์ ์ถ์ํ ๋๋ฌธ์ ORM ์ DB backend ์ ๋ํ ์ผํ ์ฌํญ๋ค์ ์จ๊ฒจ๋ฒ๋ฆฐ๋ค. ๊ฐ๋จํ ๊ฒฝ์ฐ๋ ์์ ๊ท๋ชจ์ ์์ ์์๋ ORM ์ ๋ ์ฝ๊ฒ ์ฌ์ฉํ๊ฒ ํด์ฃผ์ง๋ง, ๋ณต์ก์ฑ์ด ์ปค์ง์๋ก ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
- ORM ๊ณผ ์ง์ ์ธ์ด์ ๋ํ ์ดํด์์ด ์ฌ์ฉํ๊ฒ ๋๋ฉด ๋ฌธ์ ์ ์ํฉ๋ค์ ์ด๋ํ๊ฒ ๋๋ค. ๋๋ฒ๊น ๊ณผ ์ฑ๋ฅ์์๋ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์๋ค.
- Object ์ Relation ์ ํจ๋ฌ๋ค์์ ์ฐจ์ด์์ ์ค๋ ๋ถ์ผ์น๋ก ๊ฐ๋จํ ์ง์๋ ์ ์ ๋ณต์กํด์ง ์ฐ๋ ค๊ฐ ์๋ค.
ORM ์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์
- Raw query/query builder ๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, object-relation ๋งตํ์ ์ง์ ๊ตฌํํด์ผ ํ๋ค. ๊ฐ ์ง์์ ๋ํด์ ํ์ ์ ์์ฑํ๊ณ ๋งตํํ๋ ๋ถ๋ถ๊น์ง ๊ตฌํํ๊ฒ ๋๋ค๋ฉด, ์ ์ฒด ํ๋ก์ ํธ ์งํ์๋๊ฐ ์ง๊ธ๋ณด๋ค ๋๋์ง ๊ฒ์ด ์ฐ๋ ค๋๋ค.
- ORM ์ ๊ฐ์ฅ ํฐ ๋จ์ ์ธ object-relation ์ ๋งตํํ๋ ๊ณผ์ ์์ ๋ณต์ก์ฑ์ด ๋์ด๋ ์ฑ๋ฅ ๋ฐ ๋๋ฒ๊น ์ ์ ์ํฅ์ ์ค ์ ์๋ค๋ ์ ์ธ๋ฐ, ํ์ฌ ์ฐ๋ฆฌ ํ๋ก์ ํธ์์๋ ํ ์ด๋ธ์ ์๋ ์ ๊ณ ๋ณต์กํ ์ง์๋ฅผ ์ฌ์ฉํ ๋งํ ๊ธฐ๋ฅ๋ ์๊ธฐ์ ํฐ ์ํฅ์ ์์ ๊ฒ ๊ฐ๋ค.
๋ฐ๋ผ์ ์์ ์ด์ ๋ค๋ก ORM ์ ์ฌ์ฉํ๊ธฐ๋ก ๊ฒฐ์ !
Package | Stars | Open issues | Close issues | Last commit |
---|---|---|---|---|
Prisma | 33.5k | 2731 | 6265 | 2023.08.30 |
TypeORM | 32k | 2052 | 5594 | 2023.08.20 |
Sequelize | 28.3k | 782 | 9351 | 2023.08.30 |
ํน์ง
- ๋น๊ต์ ์ต๊ทผ์ ์ถ์๋ DB toolkit
- Prisma schema๋ก ๋ชจ๋ธ์ ์ ์ธํ๋ฉฐ, ์ ์ธ์ ๋ฐ์ดํฐ ๋ชจ๋ธ ์ ์์ ๋ง์ด๊ทธ๋ ์ด์ ์ ์ง์
- Prisma Client: Prisma schema๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ์ ์์ ํ ์ฟผ๋ฆฌ ๋น๋ ์ ๊ณต
- Prisma Migrate: Prisma schema ๊ธฐ๋ฐ์ ๋ง์ด๊ทธ๋ ์ด์ ์์คํ
- Prisma Studio: ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ง์ ์กฐ์ํ๊ณ ๋ณผ ์ ์๋ GUI ๋๊ตฌ
์ฅ์
- ๋ฐ์ดํฐ ์์ฑ ๋ฐ ์กฐํ์ ๊ฐ๋ ฅํ ํ์ ์์ ์ฑ์ ๋ณด์ฅ (์ค์ฒฉ๋ ๋ฐ์ดํฐ ์์ฑ ๋ฐ ์กฐํ ํฌํจ)
- ๋ค์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์ (PostgreSQL, MySQL, MSSQL ๋ฑ)
- SSoT(Single source of truth)์ ๋ณด์ฅํ๊ธฐ ์ํด Prisma schema ๊ธฐ๋ฐ์ผ๋ก ํ์ ์ ๋ณด ์์ฑ
- ์๋ก์ด ๊ธฐ๋ฅ ๋ฐ ์ ๋ฐ์ดํธ๊ฐ ์ง์์ ์ผ๋ก ์ด๋ฃจ์ด์ง
๋จ์
- ๋ค๋ฅธ ORM์ผ๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๊ธฐ ์ด๋ ค์ธ ์ ์์ (์์ฒด ์คํค๋ง ํ์ ์ฌ์ฉ)
- ํจํค์ง ํฌ๊ธฐ๊ฐ ํฌ๊ณ ๊ณ ๊ธ ๊ธฐ๋ฅ์ด ๋ถ์กฑํ ์ ์์
์์ ์ฝ๋
// schema.prisma
model UserModel {
id BigInt @id @default(autoincrement())
name String @db.VarChar(64)
email String @db.VarChar(64)
}
// user.repository.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const user = await prisma.user.create({
data: {
name: 'John Doe',
email: '[email protected]',
},
});
ํน์ง
- Hibernate ์ํฅ์ ๋ฐ์ TypeScript ๊ธฐ๋ฐ์ ORM
- Active Record ๋ฐ Data Mapper ํจํด ์ง์
์ฅ์
- ๋ฐ์ดํฐ ์์ฑ์ ๋ํ ํ์ ์์ ์ฑ ๋ณด์ฅ (์ค์ฒฉ๋ ๋ฐ์ดํฐ ์์ฑ ํฌํจ)
- ๋ค์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์ (MySQL, PostgreSQL, SQLite ๋ฑ)
- Active Record ๋ฐ Data Mapper ํจํด ๋ชจ๋ ์ง์ํ์ฌ ์ ์ฐํ ๊ฐ๋ฐ ๊ฐ๋ฅ
๋จ์
- ๋ฐ์ดํฐ ์กฐํ์ ๋ํ ํ์ ์์ ์ฑ์ ๋ถ๋ถ์ (์ค์ฒฉ๋ ๋ฐ์ดํฐ ์กฐํ์ ์ถ๊ฐ ์์ ํ์)
- ์ด๊ธฐ ํ์ต ๊ณก์ ์ด ๊ฐํ๋ฆ
- ์ค์ ์ด ๋ณต์กํ ์ ์์
์์ ์ฝ๋
// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column()
name: string;
@Column()
email: string;
}
// user.repository.ts
const firstUser = await conn
.getRepository(User)
.createQueryBuilder("user")
.where("user.id = :id", { id: 1 })
.getOne();
ํน์ง
- Promise ๊ธฐ๋ฐ Node.js ORM
- Active Record ํจํด ์ง์
- ๋ค์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์ (PostgreSQL, MySQL, SQLite ๋ฑ)
์ฅ์
- ๋ค์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์
- ๊ธฐ์กด์ ๋ง์ด ์ฌ์ฉ๋๊ณ ์๋ ํจํค์ง๋ก ๋ ํผ๋ฐ์ค๊ฐ ํ๋ถ
๋จ์
- ๋ฐ์ดํฐ ์์ฑ ๋ฐ ์กฐํ ๋ชจ๋ ํ์ ์์ ์ฑ ๋ถ์กฑ
- ํ๋ก์ ํธ ์งํ์ด ๋ํ๋์ด ๊ฐ๋ฐ์ด ์ ๋๋ก ๋๊ณ ์์ง ์์
์์ ์ฝ๋
const { Sequelize, Model, DataTypes } = require('sequelize');
const sequelize = new Sequelize('database', 'username', 'password', {
dialect: 'mysql',
});
class User extends Model {}
User.init({
name: DataTypes.STRING,
email: DataTypes.STRING,
}, {
sequelize,
modelName: 'user',
});
User.hasMany(Photo);
Photo.belongsTo(User);
(async () => {
await sequelize.sync({ force: true });
const user = await User.create({
name: 'John Doe',
email: '[email protected]',
});
const photo = await user.createPhoto({
url: 'example.com/photo.jpg',
});
})();
์ธ ๊ฐ์ง ํจํค์ง๋ฅผ ๋น๊ตํด๋ณด๋ฉด, Prisma๊ฐ ๋ฐ์ดํฐ ์์ฑ๊ณผ ์กฐํ ๋ชจ๋์ ๊ฐ๋ ฅํ ํ์ ์์ ์ฑ์ ์ ๊ณตํ๋ฉฐ, SSoT๋ฅผ ์ ์งํ๋ฉด์ ๋ค์ํ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ง์๊ณผ ์ง์์ ์ธ ์ ๋ฐ์ดํธ๋ก ์ธํด ๊ฐ์ฅ ์ข์ ์ ํ์ด ๋ ๊ฒ์ผ๋ก ํ๋จ.