메인 콘텐츠로 건너뛰기
이 가이드에서는 Sonamu UI를 사용하여 Post 엔티티를 만들고, 데이터베이스 테이블을 생성하며, 스캐폴딩으로 Model 파일을 자동 생성하는 방법을 알아봅니다.

전제 조건

API 서버(pnpm dev)가 실행되면 Sonamu UI가 http://localhost:1028/sonamu-ui 에서 자동으로 제공됩니다.

1단계: Sonamu UI 접속

브라우저에서 http://localhost:1028/sonamu-ui 로 접속합니다.

📸 필요: Sonamu UI 메인 화면 (Entity, Migration, Scaffolding 탭이 보이는 전체 UI)

Sonamu UI는 엔티티를 시각적으로 관리할 수 있는 웹 인터페이스입니다. 여기서 다음 작업을 수행할 수 있습니다:
  • 엔티티 생성 및 편집
  • 필드 추가/수정/삭제
  • 관계(Relation) 정의
  • Enum 타입 관리
  • Subset 정의
  • 마이그레이션 생성 및 실행
  • 스캐폴딩 (Model 파일 자동 생성)

2단계: 새 엔티티 생성

1

Entity 탭 클릭

Sonamu UI 상단 메뉴에서 Entity 탭을 클릭합니다.

📸 필요: Entity 탭 클릭 후 화면 (엔티티 목록 또는 빈 화면)

2

Add Entity 버튼

우측 상단의 Add Entity 버튼을 클릭합니다.

📸 필요: Add Entity 다이얼로그 또는 모달 창

3

기본 정보 입력

새 엔티티의 기본 정보를 입력합니다:
  • Entity ID: Post (대문자로 시작, 단수형)
  • Table Name: posts (소문자, 복수형)
  • Title: 게시글

📸 필요: Post 엔티티 기본 정보 입력 완료된 폼

Entity ID는 TypeScript 클래스명으로 사용되며, Table Name은 실제 데이터베이스 테이블명입니다.

3단계: 필드 정의

이제 Post 엔티티에 필드들을 추가합니다:
1

기본 필드 추가

Add Field 버튼을 클릭하여 다음 필드들을 추가합니다:
필드명타입설명옵션
idintegerIDPrimary Key
created_atdate생성일시DB Default: CURRENT_TIMESTAMP
titlestring제목Length: 255
contentstring내용-
authorstring작성자Length: 100
view_countinteger조회수DB Default: 0
id 필드
  • Name: id
  • Type: integer
  • Description: ID
  • Is Primary 체크
created_at 필드
  • Name: created_at
  • Type: date
  • Description: 생성일시
  • DB Default: CURRENT_TIMESTAMP
title 필드
  • Name: title
  • Type: string
  • Description: 제목
  • Length: 255
content 필드
  • Name: content
  • Type: string
  • Description: 내용
  • Length: 빈칸 (TEXT 타입으로 생성됨)
author 필드
  • Name: author
  • Type: string
  • Description: 작성자
  • Length: 100
view_count 필드
  • Name: view_count
  • Type: integer
  • Description: 조회수
  • DB Default: 0

📸 필요: 필드 추가 UI (Add Field 버튼과 모든 필드가 추가된 필드 리스트)

2

Subset 정의

Subsets 섹션에서 데이터 조회 형태를 정의합니다:
  • Subset A (All): id, created_at, title, content, author, view_count
  • Subset C (Compact): id, title, author, view_count, created_at

📸 필요: Subset 편집 화면 (Subset A와 C가 정의된 상태)

Subset은 API에서 데이터를 조회할 때 어떤 필드들을 포함할지 미리 정의하는 기능입니다. 자세한 내용은 Subset 가이드를 참고하세요.
3

Enum 정의

Enums 섹션에서 정렬 및 검색 옵션을 정의합니다:PostOrderBy
  • id-desc: ID 최신순
  • created_at-desc: 생성일시 최신순
  • view_count-desc: 조회수 높은순
PostSearchField
  • id: ID
  • title: 제목
  • author: 작성자

📸 필요: Enum 편집 화면 (PostOrderBy, PostSearchField 정의된 상태)

4

저장

Save 버튼을 클릭하여 엔티티 정의를 저장합니다.

📸 필요: 저장 완료 메시지 또는 토스트 알림

저장하면 다음 파일들이 자동으로 생성됩니다:
  • api/src/application/post/post.entity.json (엔티티 정의)
  • api/src/application/post/post.types.ts (타입 및 Zod 스키마)
  • api/src/application/sonamu.generated.ts (Base 스키마)

4단계: 데이터베이스 마이그레이션

이제 엔티티 정의를 데이터베이스에 반영해야 합니다.
1

Migration 탭으로 이동

Sonamu UI에서 Migration 탭을 클릭합니다.

📸 필요: Migration 탭 화면 (마이그레이션 목록 또는 Generate Migration 버튼)

2

마이그레이션 생성

Generate Migration 버튼을 클릭하면 새로운 마이그레이션 파일이 자동 생성됩니다.생성된 마이그레이션 SQL을 확인할 수 있습니다:
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  title VARCHAR(255) NOT NULL,
  content TEXT NOT NULL,
  author VARCHAR(100) NOT NULL,
  view_count INTEGER DEFAULT 0
);

📸 필요: 생성된 마이그레이션 SQL 미리보기 화면

3

마이그레이션 실행

Run Migration 버튼을 클릭하여 데이터베이스에 테이블을 생성합니다.

📸 필요: 마이그레이션 실행 완료 메시지 또는 성공 알림

터미널에서 다음 명령어로도 실행 가능합니다:
cd api
pnpm sonamu migrate run
4

확인

PostgreSQL 클라이언트나 pgAdmin으로 데이터베이스에 접속하여 posts 테이블이 생성되었는지 확인합니다.

5단계: 스캐폴딩 (Model 파일 자동 생성)

마이그레이션이 완료되면 Sonamu의 스캐폴딩 기능으로 Model 파일을 자동 생성할 수 있습니다.
1

Scaffolding 탭으로 이동

Sonamu UI에서 Scaffolding 탭을 클릭합니다.

📸 필요: Scaffolding 탭 화면 (엔티티 목록 및 템플릿 선택 UI)

2

Post 엔티티 선택

스캐폴딩할 엔티티로 Post를 선택합니다.
3

템플릿 선택

생성할 템플릿을 선택합니다:
  • Model - post.model.ts (비즈니스 로직 및 API 엔드포인트)
  • Model Test - post.model.test.ts (테스트 파일)

📸 필요: 템플릿 선택 UI (Model, Model Test 체크박스가 선택된 상태)

스캐폴딩은 템플릿 파일을 자동 생성하므로, 직접 코드를 작성할 필요가 없습니다. 생성된 파일은 프로젝트에 맞게 수정할 수 있습니다.
4

미리보기 (선택사항)

Preview 버튼을 클릭하면 생성될 코드를 미리 볼 수 있습니다.

📸 필요: 코드 미리보기 모달 (생성될 model.ts 코드)

5

생성

Generate 버튼을 클릭하여 파일을 생성합니다.

📸 필요: 스캐폴딩 완료 메시지 (생성된 파일 경로 표시)

다음 파일들이 생성됩니다:
  • api/src/application/post/post.model.ts
  • api/src/application/post/post.model.test.ts
터미널에서 다음 명령어로도 실행 가능합니다:
cd api
pnpm sonamu scaffold model Post
pnpm sonamu scaffold model_test Post

6단계: 생성된 Model 파일 확인

스캐폴딩으로 생성된 post.model.ts 파일을 확인해봅시다:

📸 필요: VS Code에서 열린 post.model.ts 파일 전체 화면

api/src/application/post/post.model.ts (자동 생성)
import {
  api,
  BaseModelClass,
  type ListResult,
  NotFoundException,
} from "sonamu";
import type { PostSubsetKey, PostSubsetMapping } from "../sonamu.generated";
import { postLoaderQueries, postSubsetQueries } from "../sonamu.generated.sso";
import type { Post, PostListParams, PostSaveParams } from "./post.types";

class PostModelClass extends BaseModelClass<
  PostSubsetKey,
  PostSubsetMapping,
  typeof postSubsetQueries,
  typeof postLoaderQueries
> {
  constructor() {
    super("Post", postSubsetQueries, postLoaderQueries);
  }

  @api({
    httpMethod: "GET",
    clients: ["axios", "tanstack-query"],
    resourceName: "Post",
  })
  async findById<T extends PostSubsetKey>(
    subset: T,
    id: number,
  ): Promise<PostSubsetMapping[T]> {
    const { rows } = await this.findMany(subset, {
      id,
      num: 1,
      page: 1,
    });
    
    if (!rows[0]) {
      throw new NotFoundException(`Post ID ${id} not found`);
    }

    return rows[0];
  }

  @api({
    httpMethod: "GET",
    clients: ["axios", "tanstack-query"],
    resourceName: "Posts",
  })
  async findMany<T extends PostSubsetKey>(
    subset: T,
    params?: PostListParams,
  ): Promise<ListResult<PostListParams, PostSubsetMapping[T]>> {
    const listParams = {
      num: 10,
      page: 1,
      orderBy: "id-desc" as const,
      ...params,
    };

    return await this.list(subset, listParams, (qb) => {
      // 검색 조건 추가
      if (listParams.search && listParams.searchField) {
        if (listParams.searchField === "id") {
          qb.where("id", Number(listParams.search));
        } else {
          qb.where(listParams.searchField, "like", `%${listParams.search}%`);
        }
      }

      // 정렬
      const [field, direction] = listParams.orderBy.split("-");
      qb.orderBy(field, direction as "asc" | "desc");
    });
  }

  @api({
    httpMethod: "POST",
    clients: ["axios", "tanstack-query"],
    resourceName: "Post",
  })
  async save(params: PostSaveParams): Promise<Post> {
    const id = await this.upsert(params);
    return await this.findById("A", id);
  }

  @api({
    httpMethod: "DELETE",
    clients: ["axios", "tanstack-query"],
    resourceName: "Post",
  })
  async del(id: number): Promise<void> {
    await this.deleteById(id);
  }
}

export const PostModel = new PostModelClass();
생성된 Model 파일은 기본적인 CRUD API를 포함하고 있습니다. 필요에 따라 비즈니스 로직을 추가하거나 수정할 수 있습니다.
API 서버가 HMR(Hot Module Replacement)로 자동 재시작됩니다:

📸 필요: 터미널에서 HMR 재시작 로그 (✓ Detected changes in post.model.ts)

7단계: 프론트엔드 Service 자동 생성

API를 작성하면 프론트엔드 Service 파일이 자동으로 생성됩니다.

📸 필요: VS Code에서 web/src/services/PostService.ts 파일이 생성된 화면

web/src/services/PostService.ts (자동 생성)
export class PostService {
  static async findById(subset: PostSubsetKey, id: number) {
    const res = await axios.get(`/api/posts/${id}`, {
      params: { subset }
    });
    return res.data;
  }

  static async findMany(subset: PostSubsetKey, params?: PostListParams) {
    const res = await axios.get("/api/posts", {
      params: { subset, ...params }
    });
    return res.data;
  }

  static async save(params: PostSaveParams) {
    const res = await axios.post("/api/posts", params);
    return res.data;
  }

  static async del(id: number) {
    await axios.delete(`/api/posts/${id}`);
  }
}

8단계: API 테스트

이제 API가 정상적으로 작동하는지 테스트해봅니다.

REST Client로 테스트

curl -X POST http://localhost:1028/api/posts \
  -H "Content-Type: application/json" \
  -d '{
    "title": "첫 번째 게시글",
    "content": "Sonamu로 만든 첫 번째 게시글입니다.",
    "author": "홍길동"
  }'

브라우저에서 테스트

http://localhost:1028/api/posts?subset=C 로 접속하여 JSON 응답을 확인합니다.

📸 필요: 브라우저에서 API 응답 JSON 화면 (게시글 목록 데이터)

완성! 🎉

축하합니다! 첫 번째 엔티티를 성공적으로 만들었습니다. 지금까지 다음 작업을 완료했습니다:
  • ✅ Sonamu UI로 엔티티 정의
  • ✅ 데이터베이스 테이블 생성
  • ✅ 타입 및 스키마 자동 생성
  • ✅ 스캐폴딩으로 Model 및 Test 파일 자동 생성
  • ✅ REST API 자동 생성
  • ✅ 프론트엔드 Service 자동 생성
  • ✅ API 테스트
스캐폴딩의 장점스캐폴딩을 사용하면:
  • Model 파일을 직접 작성할 필요 없이 기본 CRUD API가 자동 생성됩니다
  • 테스트 파일 템플릿도 함께 생성되어 테스트 작성이 쉬워집니다
  • 프로젝트의 일관된 코드 스타일을 유지할 수 있습니다
  • 생성된 코드는 언제든지 수정하여 비즈니스 로직을 추가할 수 있습니다

다음 단계