[NodeExpressTs] 3. 개쉬운 typeorm 사용예제
이래서 어느 세월에 컨트롤러, 서비스, 리포지토리, 메퍼, vo 구현해...
이거 무서워 nodejs 안해....
nodejs express는 crud 가 굉장히 간소하다
쿼리 돌려서 반환되는 객체 자체가 그냥 모델이다
crud에 사용하는 모듈을 typeorm을 선택하였다.
1. migration 작동됨(코드 -> 테이블)
2. model generator 작동됨(테이블 -> 코드)
3.직관적인 orm 문법
4. 쌩쿼리 지원함
참고로 필자는 쿼리빌더 예제와 쌩쿼리 예제로 보여주겠다
Postman 같은 툴로
method Post: localhost:8080/typeorm/users
이쪽으로 요청을 날리면 테이블에 테스트 데이터를 넣어줄것이다
typeorm은 3가지 스타일이 존재한다
완전 자바스크립트 문법을 따르는 orm,
Query Builder 하는 sql 문 비슷하게 코드를 짜는 스타일,
쌩쿼리,
이 3가지 스타일로 crud를 다 소개해주는건 미친짓 같으니,
간략한 예제만 보고, 이걸 기반으로
https://typeorm.io/#/update-query-builder
이 공식문서 보면서 해야한다.
공식문서 그지같은건 팩트지만 어쩔수 없다 ㅜㅜ
/** src/Router/typeorm.route.ts */
let mysql1: typeorm.Connection;
function getTypeormMysqlInstance(
req: express.Request,
res: express.Response,
next: express.NextFunction,
) {
mysql1 = req.app.get('mysql1');
next();
}
router.use(getTypeormMysqlInstance);
app.ts 에서 db에 연결한 객체를 app.set("mysql",mysql)
로 저장시킨게 기억이 나는가?
typeorm.route.ts 파일은 app.ts 에 포함된 코드가 아니다.
app.ts 와 typeorm.route.ts는 별개의 영역이다.
그래서 app.ts에서 app.set() 을 통해서 다른 파일에 있는 코드 영역에서도 꺼내쓸수있게
저장을 시킨것이다.
getTypeormMysqlInstance() 함수에서는 req.app.get() 함수를 사용하여 저장된
db객체를 꺼낸 것이다.
이제부턴 mysql.함수 이런것들을 이용해 db를 조작할수 있게됬다.
function getTypeormMysqlInstance(
req: express.Request,
res: express.Response,
next: express.NextFunction,
){}
이란 함수는 우리가 커스텀으로 만들수 있는 함수이고전문적인 용어로 말하자면 middleware 라고 한다.
이 middleware 가 매 요청마다 싱행되게 해주기 위해서
router.use(getTypeormMysqlInstance);
코드를 넣어준 것이다(app.use 와 똑같은 기능)/** js orm Insert 예제 */
router.post('/users', async (req, res) => {
try {
/** 예제에서는 orm insert 실행부분에 상수값을 박아버렸는데,
만약 클라이언트 에서 넘어온 데이터를 insert 한다고 한다면
firstName: req.body.firstName 이런식으로 해주면 된다.
node express를 클라이언트가 body에 실은 모든 primary 데이터는
req.body 안에 담겨져 있다*/
const hash = await bcrypt.hashSync('test', 10);
await mysql1.transaction(async (transactionalEntityManager) => {
const userResult = await transactionalEntityManager.save(User, {
firstName: `test${new Date().getUTCMilliseconds()}`,
lastName: `test${new Date().getUTCMilliseconds()}`,
password: hash,
});
const postResult = await transactionalEntityManager.save(Post, [
{
user: userResult,
title: `test post title${new Date().getUTCMilliseconds()}`,
text: `test post text${new Date().getUTCMilliseconds()}`,
},
{
user: userResult,
title: 'test post title2',
text: 'test post text2',
},
]);
const subPostResult = await transactionalEntityManager.save(SubPost, [
{
test: `test${new Date().getUTCMilliseconds()}`,
post: postResult[0],
},
{
test: `test${new Date().getUTCMilliseconds()}`,
post: postResult[0],
},
]);
return res.status(200).json({ userResult, postResult });
});
} catch (error: any) {
return res.status(200).json({
success: false,
data: null,
custMsg: 'transaction failed in POST:/typeorm/users',
err: error.message ?? error,
});
}
});
router.post = post 요청을 받는 라우터(컨트롤러)를 만들어주는 코드
async (req, res) => = express 제작자가 컨트롤러 함수 인자에는 이거 넣으라고 한거임.
req 는 요청에 관한 모든 데이터를 다 담고있고, req.params 이런식으로 데이터에 접근 가능함.
res 는 응답에 관한 모든 기능을 다 담고있음.
mysql1.transaction(async (transactionalEntityManager) => { ... }
db에 이미 연결된 객체를 이용하여 트랜잭션을 여는코드
const userResult = await transactionalEntityManager.save(User, {
firstName: `test${new Date().getUTCMilliseconds()}`,
... });
열린 트랜잭션 안에서 .save(entity) 함수를 이용하여 테이블에 데이터를 저장지시키는 코드참고로 query builder를 이용하여 insert나 update 시키는건 공식문서
https://typeorm.io/#/update-query-builder 여기를 참고하자
/** https://github.com/typeorm/typeorm/blob/master/docs/select-query-builder.md#joining-relations */
router.get('/users', async function (요청, 응답) {
try {
const users = await mysql1
.createQueryBuilder()
.select('user.id')
.addSelect('user.firstName')
.addSelect('user.lastName')
// User 라는 entity를 user라는 별명으로 지어줌
.from(User, 'user')
//user 테이블에 post 테이블을 leftjoin 시켜주는 코드. User entity 에 있는 posts 라는 변수를 post 라는 별명으로 지어줌
.leftJoinAndSelect('user.posts', 'post')
//post 테이블에 subpost 테이블을 leftjoin 시켜주는 코드. Post entity 에 있는 subPosts 라는 변수를 subPost 라는 별명으로 지어줌
.leftJoinAndSelect('post.subPosts', 'subPost')
.where('')
.andWhere('post.id > :id', { id: 0 })
.andWhere('subPost.id > :id', { id: 0 })
.skip(0)
.take(1000)
.getMany();
응답.status(200).json(users);
} catch (err: any) {
응답.status(200).json({
success: false,
data: null,
custMsg: '',
errMsg: err.message ?? err,
});
}
});
쿼리빌더를 사용하여 select를 란 코드.
공식문서에서 나온 예제는
await getConnection()
.createQueryBuilder('user')
이런식으로 되있는데 예제가 너무 그지같아서 필자는
await mysql1
.createQueryBuilder() ... .from(테이블이름) 이렇게 바꿨다.
공식문서의 그지같은 select 예제 뜯어 고치느라 꽤 애먹었다.
보편적으로 널리 사용되어온 sql 구조랑 별반 다를게 없다
저기서 서브쿼리를 추가하는 방법은
https://typeorm.io/#/select-query-builder / # Using subqueries
공식문서를 참고해서하면된다.
.leftJoinAndSelect('user.posts', 'post') = 부모 테이블의 레코드와 연관있는 자식 테이블을
싹 가져온다
const users = select 하고난 결과를 담고있다.
get method : localhost:8080/typeorm/users 결과
const users =[
{
"id": 3,
"firstName": "test423",
"lastName": "test423",
"posts": [
{
"id": 5,
"title": "test post title434",
"text": "test post text434",
"test": null,
"userId": 3,
"subPosts": [
{
"id": 2,
"test": "test440",
"postId": 5
},
{
"id": 3,
"test": "test440",
"postId": 5
}
]
}
]
},
{
"id": 4,
"firstName": "test102",
"lastName": "test102",
"posts": [
{
"id": 7,
"title": "test post title105",
"text": "test post text105",
"test": null,
"userId": 4,
"subPosts": [
{
"id": 4,
"test": "test109",
"postId": 7
},
{
"id": 5,
"test": "test109",
"postId": 7
}
]
}
]
}
]
결과는 스프링의 mybatis resultmap 을 쓴것처럼 나온다
/** typeorm 쌩쿼리, 부분쌩쿼리
* https://darrengwon.tistory.com/m/1323?category=892587
*
* https://github.com/typeorm/typeorm/issues/881
* - input symbol is vary by DB
* mysql : ?
*/
router.get('/rawquery', async function (요청, 응답) {
try {
let testInput = " '' OR 1=1 ";
const users = await mysql1.query(
`
SELECT
*
FROM user as u
LEFT JOIN post as p ON u.id=p.userId AND p.id > ?
WHERE u.firstName != ?
`,
[0, testInput],
);
응답.status(200).json(users);
} catch (err: any) {
응답.status(200).json({
success: false,
data: null,
custMsg: '',
errMsg: err.message ?? err,
});
}
});
쌩쿼리는 await mysql1.query(
...
`
뭐뭐 ` []); 안에 써주면 된다.
js의 강력한 backtick 기능을 이용해서 작성한다
where 조건에 쓰일 인자값은 ?표로 표시하고,
쿼리문 다음 인자 [] 안에 ?표 위치에 알맞는 값들을 나열해주면 된다
쌩쿼리의 단점은 mybatis의 resultmap 기능을 못사용한다는 것이다.
하지만 api 설계와 forloop 적절히 섞으면 문제 해결이 되기도 한다.
스프링과 같이 controller, service, mapper, mybatis 부분이 나눠져있지 않고
저렇게 한 영역 안에서 crud를 다 표현할수 있는 스타일을 minimap api 하고
직접 사용해보면 보일러 코드가 하나도 없어서 굉장히 간소하게 여러 업무들을 해결할수 있다.
만약에 추천 시스템과같은 긴 코드들이나 몇천줄 되는 쿼리들은 router 폴더안에 파일하나
새로 파줘서 리턴값만 router 쪽으로 넘기면 코드관리도 깔끔하게 할수있다.
이렇게 만든 router를 app.ts 에서 app.use() 시켜주면 되는것이다
src/app.ts
import typeormRouter from './Router/typeorm/typeorm.route';
app.use('/typeorm', typeormRouter);
댓글
댓글 쓰기