๐์ฑํฅํ ์คํธ ๊ธฐ๋ฐ AI ์๊ธ์ ์ถ์ฒ ์๋น์ค , ๋ฌด๋ฌผ ๋ฐ๋ก๊ฐ๊ธฐ๐
moo-mool
[LG U+ ์ ๋ ์นด 2๊ธฐ] ํ๋ก ํธ์๋ ์ข ํฉํ๋ก์ ํธ 1์กฐ (UgodIT). moo-mool has 4 repositories available. Follow their code on GitHub.
github.com
๋ค์ด๊ฐ๋ฉฐ
ํ๋ก์ ํธ๋ฅผ ์์ํ ๋ ์๋ ๊ตฌ์์ ๋ฐฑ์๋๋ Java Spring, ํ๋ก ํธ์๋๋ Next.js๋ก ๋ถ๋ฆฌํ๋ ๋ฐฉ์์ด์์ต๋๋ค. ํ์ง๋ง ์ ํฌ ํ์ ๋ชจ๋ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๋ก, Java๋ณด๋ค๋ TypeScript ํ๊ฒฝ์ ํจ์ฌ ์ต์ํ์ต๋๋ค. ๋, ์ด๋ฒ ํ๋ก์ ํธ๋ AI๋ฅผ ํ์ฉํด์ผ ํ๋ ๋ช ํํ ์๊ตฌ์ฌํญ์ด ์์๊ธฐ ๋๋ฌธ์ ๋ฐฑ์๋์ ๊ณ ๋ํ๋ณด๋ค ํ๋ก ํธ์๋ ์์ฑ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ๋ ์ค์ํ ๋ชฉํ๋ก ๋์์ต๋๋ค. ์์ฐ์ค๋ฝ๊ฒ ๊ฐ๋ฐ ๋ฆฌ์์ค๋ฅผ ์ต์ํํ ์ ์๋ ๋ฐฉํฅ์ ์ฐพ๊ฒ ๋์์ต๋๋ค.
๊ทธ๋ฌ๋ ์ค Next.js์ API Routes ๊ธฐ๋ฅ์ ํ์ฉํ๋ฉด Node.js ๊ธฐ๋ฐ์ผ๋ก ํ์ํ ์์ค์ ๋ฐฑ์๋ API๋ฅผ ์ง์ ์์ฑํ ์ ์๊ณ , Prisma ORM์ ํตํด MYSQL ์ฐ๋๊น์ง ๊ฐ๋ฅํ๋ค๋ ์ ์ ํ์ธํ์ต๋๋ค. ์ฆ, NextJS ํ๋๋ก FE์ BE๋ฅผ ๋ชจ๋ ์ปค๋ฒํ ์ ์์์ต๋๋ค
NextAuth ์ฌ์ฉํ ์ด์
๊ฐ์ฅ ๋จผ์ ํด๊ฒฐํด์ผ ํ๋ ๊ณผ์ ๊ฐ ์นด์นด์ค ๋ก๊ทธ์ธ์ ํฌํจํ ์ธ์ฆ์ด์์ต๋๋ค. ์ธ์ฆ์ ์ง์ ๊ตฌํํ ์๋ ์์ง๋ง, OAuth ํ๋กํ ์ฝ ์ฒ๋ฆฌ, ํ ํฐ ์ฌ๋ฐ๊ธ, ์ธ์ ๊ด๋ฆฌ๊น์ง ์ ๋ถ ์ ๊ฒฝ ์ฐ๋ ๊ฑด ๋ฆฌ์คํฌ์ ๋น์ฉ์ด ์ปธ์ต๋๋ค. ํ ๋ฆฌ์์ค๋ฅผ ๊ณ ๋ คํ์ ๋, ๋ณด์ ๋ก์ง์ ์ง์ ์ง๊ธฐ๋ณด๋ค๋ ๊ฒ์ฆ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋์ ํด ๊ฐ๋ฐ ์๋๋ฅผ ๋์ด๊ณ ์์ ์ฑ์ ํ๋ณดํ๋ ๊ฒ ๋ ํฉ๋ฆฌ์ ์ด์์ต๋๋ค.
1. ์นด์นด์ค OAuth Provider ์ง์
// ์นด์นด์ค OAuth ์ค์ - ์ปค์คํ
๊ตฌํ ์์ด ๋ฐ๋ก ์ ์ฉ
import KakaoProvider from "next-auth/providers/kakao"
๋ณ๋ ์ปค์คํ ์์ด KakaoProvider๋ฅผ ๋ถ๋ฌ์ ๋ฐ๋ก ์ ์ฉ ๊ฐ๋ฅํด ์ด๊ธฐ ๊ตฌ์ถ ์๋๋ฅผ ๋จ์ถํ ์ ์์์ต๋๋ค.
2. ์ธ์ ๊ด๋ฆฌ ๊ฐ์ํ
// ํ ์ค๋ก ์ฌ์ฉ์ ์ ๋ณด ์ ๊ทผ ๊ฐ๋ฅ
const { data: session } = useSession()
JWT ๊ธฐ๋ฐ ์ธ์
์ ๊ธฐ๋ณธ ์ ๊ณตํด ์๋ฒ·ํด๋ผ์ด์ธํธ ์ด๋์๋ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ ๊ทผํ ์ ์์์ต๋๋ค.
→ ๋ก๊ทธ์ธ → ์ฌ์ฉ์ ์ ๋ณด ํ์ธ → DB ์ ์ฅ ํ๋ฆ์ด ์ผ๊ด์ฑ ์๊ฒ ์ด์ด์ก์ต๋๋ค.
3. ํ์ฅ์ฑ
์นด์นด์ค ์ธ์๋ Google, Github ๋ฑ ๋ค์ํ Provider๋ฅผ ์ฝ๊ฒ ๋ถ์ผ ์ ์์ด, ์๋น์ค ํ์ฅ ์ ๋ก๊ทธ์ธ ์ฑ๋์ ๋๋ฆฌ๊ธฐ ์์ํ์ต๋๋ค.
ํนํ ์นด์นด์ค ๋ก๊ทธ์ธ์ ์งํํ๊ณ ์ฌ์ฉ์ ์ ๋ณด์ ์ ๊ทผํ๋ ค๊ณ ํ ๋
CONST {data: session} = useSession()
์ด๋ ๊ฒ ๋จ ํ ์ค๋ง ์์ฑํ๋ฉด session ๊ฐ์ฒด ์์์ ๋ก๊ทธ์ธํ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ฐ๋ก ํ์ฉํ ์ ์์์ต๋๋ค. ์ด๋ ์ง์ OAuth๋ฅผ ๊ตฌํํ์ ๋์ ๋ณต์กํ ํ ํฐ ํ์ฑ, API ํธ์ถ ๊ณผ์ ์ ์๋ตํ๊ณ ํ์ค๋ก ์์ฑํ ์ ์์ด์ ๋งค์ฐ ํธํ์ต๋๋ค.
๊ฒฐ๊ตญ, Nextjs ํ์คํ ํ๊ฒฝ์์ ์ธ์ฆ๋ ์ด์ด๊น์ง ์์ฐ์ค๋ฝ๊ฒ NextJS ์ํ๊ณ์ ๋ง์ถฐ ํด๊ฒฐํ ์ ์์์ต๋๋ค.
ORM : Prisma ์ ํ
NextAuth๋ก ์ธ์ฆ์ ํด๊ฒฐํ ๋ค, ๋ฐ์ดํฐ ์ ์ฅ์๋ก๋ MySQL์ ์ ํํ์ต๋๋ค. MySQL์ ๊ฐ์ฅ ๋์ค์ ์ด๊ณ ์์ ์ ์ธ ๊ด๊ณํ DB์๊ณ , ์ ํฌ ํ์ ์ ๋ ์นด ๊ต์ก ๊ณผ์ ์์ ์ด๋ฏธ Java + Spring + MySQL ์กฐํฉ์ ๊ฒฝํํ ์ ์ด ์์ด ๋น ๋ฅด๊ฒ ์ ์ํ ์ ์์์ต๋๋ค.
๋ค๋ง SQL์ ์ง์ ์์ฑํ๋ ๋ฐฉ์์ ํ์ ๊ณผ ์ ์ง๋ณด์์ ๋ถํธํจ์ด ๋ง์์ต๋๋ค. ๊ทธ๋์ ORM(Object Relational Mapping) ๋๊ตฌ๋ฅผ ๋์ ํ๊ธฐ๋ก ํ๊ณ , ๊ทธ์ค์์๋ Prisma๋ฅผ ์ ํํ์ต๋๋ค.
Java Spring๊ณผ Next.js๋น๊ต
1. ORM ์ธก๋ฉด : Java์ MyBatis vs. Next.js์ Prisma
ํ์ ์์ ์ฑ , ์๋ ๋ง์ด๊ทธ๋ ์ด์
MyBatis๋ SQL ์ค์ฌ์ Mapper ๊ธฐ๋ฐ ORM์ผ๋ก ๊ฐ๋ฐ์๊ฐ ์ง์ SQL์ ์์ฑํ๊ณ ์ด๋ฅผ XML ํน์ annotation์ ํตํด ๊ฐ์ฒด์ ๋งคํํฉ๋๋ค.
// MyBatis + Spring ์์
@PostMapping
public ResponseEntity<String> createPost(@RequestBody PostDto postDto) {
postService.createPost(postDto);
}
<!-- PostMapper.xml -->
<insert id="insertPost">
INSERT INTO posts (title, content) VALUES (#{title}, #{content})
</insert>
์ด๋ ๋ณต์กํ ์ฟผ๋ฆฌ๋ DB์ ํนํ๋ ๋ก์ง์ ์ธ๋ฐํ๊ฒ ์ ์ด๊ฐ๋๋ฐ ๊ฐ์ ์ด ์์ผ๋ฉฐ, ๋๊ท๋ชจ ๊ธฐ์ ์์คํ ์์ ์ ํธํ๋ ๋ฐฉ์์ ๋๋ค. ๋ค๋ง, ์ฟผ๋ฆฌ ์์ฑ๊ณผ ๋งคํ ์ค์ ์ ๋ํ ๊ฐ๋ฐ์ ์์กด๋๊ฐ ๋์ ์์ฐ์ฑ๊ณผ ์ ์ง ๋ณด์ ์ธก๋ฉด์์ ๋ถ๋ด์ด ์์์ต๋๋ค.
๋ฐ๋ฉด, Prisma๋ ์ฝ๋ ๊ธฐ๋ฐ์ผ๋ก ์คํค๋ง๋ฅผ ์ ์ธํ๊ณ ์ด๋ฅผ ํตํด ํ์ ์์ ์ฑ๊ณผ ์๋ ์ฟผ๋ฆฌ ์์ฑ์ ์ง์ํ๋ ORM์ผ๋ก, SQL ์ถ์ํ ์์ค์ด ๋๊ณ ํ์ ์คํฌ๋ฆฝํธ์ ์์ฐ์ค๋ฝ๊ฒ ํตํฉ๋๋ฉฐ ๋ฐ์ดํฐ ๋ชจ๋ธ๋ง ๋ฐ ๋ง์ด๊ทธ๋ ์ด์ ์ด ํจ์จ์ ์ ๋๋ค.
1. ํ์
์์ ์ฑ
- Prisma๋ TypeScript ํตํฉ์ ํตํด ์ฝ๋์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ๊ฐ ํ์ ๋ถ์ผ์น ๊ฐ์
- ์ปดํ์ผ ๋จ๊ณ์์ ์ค๋ฅ ๊ฒ์ฆ ๊ฐ๋ฅ
// prisma/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
}
2. ์๋ ๋ง์ด๊ทธ๋ ์ด์ -> ๋น ๋ฅธ ๊ฐ๋ฐ ์๋
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์คํค๋ง ๋ณ๊ฒฝ ์ ์๋์ผ๋ก SQL ๋ง์ด๊ทธ๋ ์ด์ ์์ฑํ๊ณ ์ ์ฉ, ๊ฐ๋ฐ์๊ฐ ์๋์ผ๋ก ์คํค๋ง๋ฅผ ์ ๋ฐ์ดํธํ๋ ์๊ณ ๋ฅผ ๋์ด์ค๋๋ค.
3. ๊ฐ๋ฐ ํจ์จ์ฑ
- ๊ฐ๊ฒฐํ API(prisma.user.findUnique, prisma.post.create ๋ฑ)๋ฅผ ์ ๊ณตํ์ฌ ๋ฐ๋ณต์ ์ธ CRUD ์ฝ๋๋ฅผ ๋จ์ถ์์ผ์ค๋๋ค
// Next.js + Prisma ์์
const post = await prisma.post.create({
data: { title, content },
});
์ด๋ ๋น ๋ฅธ ๊ฐ๋ฐ๊ณผ ์ผ๊ด๋ DB ๊ตฌ์กฐ ์ ์ง์ ์ ๋ฆฌํ๋ฉฐ ํ์ TS ๊ธฐ๋ฐ ํ๋ก ํธ์๋ ๊ฒฝํ๊ณผ๋ ์ ๋ง์์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ์ ํฌ๋ ๋น ๋ฅธ ๊ฐ๋ฐ๊ณผ ํตํฉ๋ ๊ธฐ์ ์คํ์ ์ค์ฌ์ผ๋ก Prisma ORM์ ์ ํํ์ต๋๋ค.
2. ์ํคํ ์ฒ ๋น๊ต : Java์ MyBatis ๊ณ์ธตํ vs. Prisma API ์ค์ฌ ๊ตฌ์กฐ
๋ณต์กํ ๊ณ์ธต์ ์ต์ํํ๊ณ ์๋ํฌ์ธํธ ์ค์ฌ์ผ๋ก ๋น ๋ฅด๊ฒ API๋ฅผ ๊ตฌํํ ์ ์์ด, ์ด๊ธฐ ๊ฐ๋ฐ ์๋์ ํ์ ํจ์จ ํฅ์
Java๋ ์ ํ์ ์ธ ๊ณ์ธตํ ์ํคํ ์ฒ๋ก Controller -> Servie -> Repository ๊ตฌ์กฐ๋ก ๋ช ํํ๊ฒ ์ญํ ์ ๋ถ๋ฆฌํฉ๋๋ค. ์ด๋ ๋๊ท๋ชจ ์์คํ ์ ์ ํฉํ๋ฉฐ ํ ์คํธ ์ ์ง๋ณด์ ๋ณด์ ์ธก๋ฉด์๋ ์ ๋ฆฌํฉ๋๋ค. ๊ทธ๋ฌ๋ ์ด๊ธฐ ์ง์ ์ฅ๋ฒฝ์ด ๋๊ณ ๊ฐ๋จํ ๊ธฐ๋ฅ ๊ตฌํ์๋ ์ฝ๋๊ฐ ๋ถ์ฐ๋๊ณ ๋ณต์กํด์ง ์ ์์ต๋๋ค.
[Controller] → [Service] → [Mapper] → [DB]
๋ฐ๋ฉด, Nextjs์ API Route ๊ตฌ์กฐ๋ ๋ผ์ฐํ ๊ณผ ๋ก์ง์ด ํ์ผ๋จ์๋ก ๋ฌถ์ฌ ์์ด, ์๋ํฌ์ธํธ ์ค์ฌ์ผ๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ๋ณต์กํ ๊ณ์ธต ์์ด๋ ๊ฐ๋จํ API ๊ธฐ๋ฅ์ ๋น ๋ฅด๊ฒ ๊ตฌํํ ์ ์์ผ๋ฉฐ, page/api/* ํด๋ ๋ง์ผ๋ก Rest API๋ฅผ ๊ตฌ์ฑํ ์ ์๋ค๋ ์ ๋ ์ฅ์ ์ ๋๋ค. ๋ค๋ง ๊ท๋ชจ๊ฐ ์ปค์ง์๋ก ๋ชจ๋ํ ์ค๊ณ๋ ๋ฏธ๋ค์จ์ด ๊ตฌ์ฑ์ด ํ์ํฉ๋๋ค.
[API Route] → [Prisma Client] → [DB]
์ด๋ฌํ ๊ตฌ์กฐ์ ๊ฐ๊ฒฐํจ์ ํ๋ก์ ํธ ์ด๊ธฐ์ ์ฅ์ ์ผ๋ก ์์ฉํ์์ต๋๋ค.
๊ฒ์๊ธ ์์ฑ API ์์ ๋น๊ต
๊ฒ์๊ธ์ ์์ฑํ๋ API๋ฅผ ๊ฐ๊ฐ Java Spring๊ณผ Next๋ก ๊ตฌํํด Next Prisma์ ๊ตฌ์กฐ์ ๋จ์ํจ์ ํ์ธํด๋ณด๊ฒ ์ต๋๋ค.
Java Spring + MyBatis (๊ณ์ธตํ)
// PostController.java
@RestController
@RequestMapping("/api/posts")
public class PostController {
private final PostService postService;
public PostController(PostService postService) { this.postService = postService; }
@PostMapping
public ResponseEntity<PostDto> create(@RequestBody PostDto dto) {
return ResponseEntity.ok(postService.create(dto));
}
}
์ฌ์ฉ์๊ฐ /api/posts ๋ก POST ์์ฒญ์ ๋ณด๋ด๋ฉด PostController ๊ฐ ์ด๋ฅผ ๋ฐ๊ณ , ์ค์ ๋ก์ง ์ฒ๋ฆฌ๋ฅผ ์ํด PostService๋ก ์ ๋ฌํฉ๋๋ค.
// PostService.java
@Service
public class PostService {
private final PostMapper postMapper;
public PostService(PostMapper postMapper) { this.postMapper = postMapper; }
public PostDto create(PostDto dto) {
postMapper.insertPost(dto);
return dto;
}
}
// PostMapper.java
@Mapper
public interface PostMapper {
void insertPost(PostDto dto);
}
Mapper ์ธํฐํ์ด์ค๋ฅผ ํตํด ์ค์ SQL์ ์คํํ๋ XML ํ์ผ๊ณผ ์ฐ๊ฒฐํฉ๋๋ค.
<!-- PostMapper.xml -->
<mapper namespace="com.example.PostMapper">
<insert id="insertPost" parameterType="com.example.PostDto">
INSERT INTO posts (title, content) VALUES (#{title}, #{content})
</insert>
</mapper>
SQL ๋ฌธ์ด Mapper ์ธํฐํ์ด์ค์ ๋งคํ๋์ด ์คํ๋ฉ๋๋ค.
// PostDto.java
@Data
public class PostDto {
private String title;
private String content;
}
๋ฐ์ดํฐ๋ฅผ ๋ด๋ DTO ๊ฐ์ฒด
๊ฒ์๊ธ POST API ํ๋๋ฅผ ๋ง๋ค๊ธฐ ์ํด ์ด 5๊ฐ์ ๊ณ์ธต์ ํ์ผ์ด ํ์ํฉ๋๋ค.
Nextjs (API Route + Prisma)
// prisma/schema.prisma
model Post {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
}
// app/api/posts/route.ts (Next.js App Router)
import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
export async function POST(req: Request) {
const { title, content } = await req.json();
const post = await prisma.post.create({ data: { title, content } });
return NextResponse.json(post, { status: 200 });
}
shema.prisma ํ์ผ 1๊ฐ๋ก ๋ฐ์ดํฐ ํ
์ด๋ธ์ ์ ์ํ๊ณ , NextJS์ API Route ์์์ Prisma Client๋ฅผ ๋ฐ๋ก ํธ์ถํด ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์๋ํฌ์ธํธ๋ฅผ ๋์์ ์์ฑํ ์ ์์ต๋๋ค.
๋ง๋ฌด๋ฆฌํ๋ฉฐ
Java Spring์ ๋๊ท๋ชจ ์์คํ ๊ตฌ์ถ์ ์ ํฉํ ์์ ์ฑ๊ณผ ํ์ฅ์ฑ์ ๊ฐ์ถ ํ๋ ์ธ์ํฌ๋ก, ๊ฒฌ๊ณ ํ ์ํคํ ์ฒ ์ค๊ณ๊ฐ ํ์ํ ํ๊ฒฝ์์ ์ ํฉํฉ๋๋ค.
๋ฐ๋ฉด, NextJS๋ ํ๋ก ํธ์๋์ ๋ฐฑ์๋๋ฅผ ํตํฉํด ๋น ๋ฅด๊ณ ๊ฐ๋จํ๊ฒ ๊ฐ๋ฐํ ์ ์๊ณ , TypeScript ๊ธฐ๋ฐ์ Prisma ORM๊ณผ์ ์กฐํฉ์ ํตํด ๋น๊ต์ ๊ฐ๋ฒผ์ด ์๋น์ค ๊ตฌ์กฐ์ ์ ํฉํ ์์ฐ์ฑ๊ณผ ์ ์ฐ์ฑ์ ์ฅ์ ์ด ์์ต๋๋ค.
์ฆ, Nextjs + Prisma ์กฐํฉ์ ์ ํ๋ ๋ฆฌ์์ค์ ์๊ฐ ๋ด์์ ์์ ์ ์ด๊ณ ํ์ฅ ๊ฐ๋ฅํ ํ์คํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ตฌ์ถํ ์ ์๊ณ , ํ๋ก ํธ์๋ ์ค์ฌ์ ํ ๊ตฌ์ฑ์ผ๋ก ๋ฐฑ์๋ ๋ณต์ก์ฑ์ ์ต์ํํ๋ฉด์๋ ํ์ํ ๊ธฐ๋ฅ์ ๋น ๋ฅด๊ฒ ๊ตฌํํ ์ ์๋ค๋ ์ฅ์ ์ด ์์ด ์ ํํ์์ต๋๋ค.
์ฐธ๊ณ
Prisma๋? ๊ธฐ์ ๋ค์ ์ฌ์ฉ ์ฌ๋ก๋ถํฐ ์ ์ฉ ๋ฐฉ๋ฒ๊น์ง์ ์ฌ์ฉ ์ข ํฉ ๊ฐ์ด๋ I ์ด๋์ ๋ธ๋ก๊ทธ
๋ณต์กํ ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ ๋, ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์ฐ๋์ ๋์ฑ ์ค์ํด์ง๋๋ฐ, ์ด๋ด ๋ ๋ฐ์ดํฐ ๋ฒ ์ด์ค ์์ ์ ํจ์จ์ ์ผ๋ก ๋ง๋ค์ด์ฃผ๋ 'Prisma'์ ์ฌ์ฉ๋ฒ์ ๋ํด ์ด๋์์์ ์์ธํ ์๋ ค๋๋ฆฌ๊ฒ
www.elancer.co.kr
