> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 첫 엔티티 만들기

> Sonamu UI로 첫 번째 엔티티를 생성하고 API 만들기

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

## 전제 조건

* [빠른 시작](/ko/getting-started/quick-start) 가이드를 완료하고 개발 서버가 실행 중이어야 합니다.
* API 서버가 [http://localhost:34900](http://localhost:34900) 에서 실행 중이어야 합니다.

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

## 1단계: Sonamu UI 접속

브라우저에서 [http://localhost:34900/sonamu-ui](http://localhost:34900/sonamu-ui) 로 접속합니다.

<Frame caption="Sonamu UI 메인 화면 (Entity, Migration, Scaffolding 탭)">
  <img src="https://mintcdn.com/cartanova-7788888c/wk-O_9zBoKGthXNV/images/sonamuUI1.png?fit=max&auto=format&n=wk-O_9zBoKGthXNV&q=85&s=637d6e1a97b920da63a7409013c92159" alt="Sonamu UI 메인 화면" style={{ width: "100%", borderRadius: "0.75rem" }} width="3456" height="1916" data-path="images/sonamuUI1.png" />
</Frame>

Sonamu UI는 엔티티를 시각적으로 관리할 수 있는 웹 인터페이스입니다. 여기서 다음 작업을 수행할 수 있습니다:

* 엔티티 생성 및 편집
* 프롭 추가/수정/삭제
* 관계(Relation) 정의
* Enum 타입 관리
* Subset 정의
* 마이그레이션 생성 및 실행
* 스캐폴딩 (Model 파일 자동 생성)

## 2단계: 새 엔티티 생성

<Steps>
  <Step title="Entity 탭 클릭">
    Sonamu UI 상단 메뉴에서 **Entity** 탭을 클릭하여 현재 존재하는 엔티티를 확인합니다.

    <Frame caption="Entity 탭 화면">
      <img src="https://mintcdn.com/cartanova-7788888c/wk-O_9zBoKGthXNV/images/sonamuUI1.png?fit=max&auto=format&n=wk-O_9zBoKGthXNV&q=85&s=637d6e1a97b920da63a7409013c92159" alt="Entity 탭" style={{width: '100%', borderRadius: '0.75rem'}} width="3456" height="1916" data-path="images/sonamuUI1.png" />
    </Frame>
  </Step>

  <Step title="New Entity 버튼 클릭">
    좌측 사이드바 하단에 있는 **New Entity** 버튼을 클릭합니다.

    <Frame caption="엔티티 생성 모달 창">
      <img src="https://mintcdn.com/cartanova-7788888c/wk-O_9zBoKGthXNV/images/sonamuUI-create-entity.png?fit=max&auto=format&n=wk-O_9zBoKGthXNV&q=85&s=5c462c85be483a7b9126a9f1e20c1045" alt="엔티티 생성 모달" style={{width: '100%', borderRadius: '0.75rem'}} width="1844" height="750" data-path="images/sonamuUI-create-entity.png" />
    </Frame>
  </Step>

  <Step title="기본 정보 입력 및 생성">
    새 엔티티의 기본 정보를 입력하고 **Create** 버튼을 클릭합니다:

    * **Entity ID**: `Post` (대문자로 시작, 단수형)
    * **Table Name**: `posts` (소문자, 복수형)
    * **Title**: `게시글`

    <Frame caption="Create 버튼 클릭 후 생성된 Post 엔티티 화면">
      <img src="https://mintcdn.com/cartanova-7788888c/wk-O_9zBoKGthXNV/images/sonamuUI-created-entity.png?fit=max&auto=format&n=wk-O_9zBoKGthXNV&q=85&s=8a0bfe09c51843f4ca9fad863996edc5" alt="생성된 Post 엔티티" style={{ width: "100%", borderRadius: "0.75rem" }} width="2872" height="1916" data-path="images/sonamuUI-created-entity.png" />
    </Frame>

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

## 3단계: 프롭 정의

이제 `Post` 엔티티에 프롭들을 추가합니다:

<Steps>
  <Step title="기본 프롭 추가">
    **Add a prop** 버튼을 클릭하여 다음 프롭들을 추가합니다:

    | 프롭명          | 타입      | 설명   | 옵션                              |
    | ------------ | ------- | ---- | ------------------------------- |
    | `id`         | integer | ID   | Primary Key                     |
    | `created_at` | date    | 생성일시 | DB Default: `CURRENT_TIMESTAMP` |
    | `title`      | string  | 제목   | Length: 255                     |
    | `content`    | string  | 내용   | -                               |
    | `author`     | string  | 작성자  | Length: 100                     |
    | `view_count` | integer | 조회수  | DB Default: `0`                 |

    <Accordion title="각 프롭 설정 방법">
      **id 프롭**

      * Name: `id`
      * Type: `integer`
      * Description: `ID`

      **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`
    </Accordion>

    <Frame caption="프롭 추가 UI (Add a prop 버튼과 모든 프롭이 추가된 프롭 리스트)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-add-a-prop.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=22a619080a692b5f5461793f1b8a30eb" alt="프롭 추가 UI" style={{ width: "100%", borderRadius: "0.75rem" }} width="1698" height="728" data-path="images/sonamuUI-add-a-prop.png" />
    </Frame>
  </Step>

  <Step title="Subset 정의">
    **Subsets** 섹션에서 데이터 조회 형태를 정의합니다:

    * **Subset A** (Admin): `id`, `created_at`, `title`, `content`, `author`, `view_count` - 전체 정보
    * **Subset P** (Public): `id`, `title`, `author`, `created_at` - 공개 정보
    * **Subset SS** (Secure Short): `id`, `title`, `author` - 요약 정보

    <Frame caption="Subset 편집 화면 (Subset A, P, SS가 정의된 상태)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-subsets.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=453587fdd56979c99fe0badfd1a398fe" alt="Subset 편집 화면" style={{ width: "400px", borderRadius: "0.75rem" }} width="858" height="686" data-path="images/sonamuUI-subsets.png" />
    </Frame>

    <Note>
      Subset은 API에서 데이터를 조회할 때 어떤 프롭들을 포함할지 미리 정의하는
      기능입니다. 자세한 내용은 [Subset
      가이드](/ko/database/subsets/what-are-subsets)를 참고하세요.
    </Note>
  </Step>

  <Step title="Enum 정의">
    **Enums** 섹션에서 정렬 및 검색 옵션을 정의합니다:

    **PostOrderBy**

    * `id-desc`: ID 최신순
    * `created_at-desc`: 생성일시 최신순
    * `view_count-desc`: 조회수 높은순

    **PostSearchField**

    * `id`: ID
    * `title`: 제목
    * `author`: 작성자

    <Frame caption="Enum 편집 화면 (PostOrderBy, PostSearchField 정의된 상태)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-enums.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=2f16991b67226b45b80480c74d0e7802" alt="Enum 편집 화면" style={{ width: "800px", borderRadius: "0.75rem" }} width="1440" height="542" data-path="images/sonamuUI-enums.png" />
    </Frame>
  </Step>
</Steps>

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

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

<Steps>
  <Step title="Migration 탭으로 이동">
    Sonamu UI에서 **Migration** 탭을 클릭합니다.

    <Frame caption="Migration 탭 화면 (마이그레이션 목록 또는 Generate Migration 버튼)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-db-migration2.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=a5b7a223a30d2d8a42da6a3a397cdd2f" alt="Migration 탭 화면" style={{width: '100%', borderRadius: '0.75rem'}} width="2594" height="1352" data-path="images/sonamuUI-db-migration2.png" />
    </Frame>
  </Step>

  <Step title="마이그레이션 생성">
    **Generate** 버튼을 클릭하면 새로운 마이그레이션 Code File이 자동 생성됩니다.

    생성된 파일 목록에서 `<>` 버튼을 클릭하면 Cursor의 해당 마이그레이션 파일 페이지가 열립니다. 파일 내용은 다음과 같습니다:

    <Frame caption="Cursor에서 열린 마이그레이션 파일">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-db-migration3.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=0429a7004207d33e86a1f5927f48a097" alt="Cursor에서 열린 마이그레이션 파일" style={{width: '100%', borderRadius: '0.75rem'}} width="1478" height="1052" data-path="images/sonamuUI-db-migration3.png" />
    </Frame>
  </Step>

  <Step title="마이그레이션 실행">
    Migration 탭에서 pending 상태의 마이그레이션을 확인할 수 있습니다.

    **all** 버튼을 클릭한 후 **Apply to Latest**를 클릭하면 모달 창에서 migration targets를 확인할 수 있습니다.

    **Shadow DB Testing**을 선택한 후 **commit** 버튼을 클릭하면 데이터베이스 테이블이 생성됩니다.

    <Frame caption="Migration 실행 완료">
      <video autoPlay muted loop playsInline controls className="w-full rounded-xl" src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUi-db-migration2-generate.mp4?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=e9aacfcd59bd521d77707c78d3e0b623" data-path="images/sonamuUi-db-migration2-generate.mp4" />
    </Frame>
  </Step>

  <Step title="확인">
    PostgreSQL 클라이언트나 pgAdmin으로 데이터베이스에 접속하여 `posts` 테이블이 생성되었는지 확인합니다.
  </Step>
</Steps>

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

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

<Steps>
  <Step title="Scaffolding 탭으로 이동">
    Sonamu UI에서 **Scaffolding** 탭을 클릭합니다.

    <Frame caption="Scaffolding 탭 화면 (엔티티 목록 및 템플릿 선택 UI)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-scaffolding1.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=4276df31716045addf9af3786af7ee8d" alt="Scaffolding 탭 화면" style={{width: '100%', borderRadius: '0.75rem'}} width="2610" height="1058" data-path="images/sonamuUI-scaffolding1.png" />
    </Frame>
  </Step>

  <Step title="Post 엔티티 선택">스캐폴딩할 엔티티로 **Post**를 선택합니다.</Step>

  <Step title="템플릿 선택">
    생성할 템플릿을 선택합니다:

    * ✅ **Model** - `post.model.ts` (비즈니스 로직 및 API 엔드포인트)
    * ✅ **Model Test** - `post.model.test.ts` (테스트 파일)

    <Frame caption="템플릿 선택 UI (Model, Model Test 체크박스가 선택된 상태)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-scaffolding2.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=fcf2ec2f655b636459f01bc91bb4ce9e" alt="템플릿 선택 UI" style={{ width: "100%", borderRadius: "0.75rem" }} width="2608" height="1148" data-path="images/sonamuUI-scaffolding2.png" />
    </Frame>

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

  <Step title="미리보기 (선택사항)">
    **Preview** 버튼을 클릭하면 생성될 코드를 미리 볼 수 있습니다.

    <Frame caption="코드 미리보기 모달 (생성될 model.ts 코드)">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-scaffolding3.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=d0770e413ffbf7b700345f85ae1b8376" alt="코드 미리보기 모달" style={{width: '100%', borderRadius: '0.75rem'}} width="2618" height="2034" data-path="images/sonamuUI-scaffolding3.png" />
    </Frame>
  </Step>

  <Step title="생성">
    **Generate** 버튼을 클릭하여 파일을 생성합니다.

    <Frame caption="생성된 파일 목록">
      <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-scaffolding5.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=5d9a87c29279f7eeb911d7bc7e4eb77c" alt="생성된 파일 목록" style={{ width: "100%", borderRadius: "0.75rem" }} width="1548" height="442" data-path="images/sonamuUI-scaffolding5.png" />
    </Frame>

    다음 파일들이 생성됩니다:

    * `api/src/application/post/post.model.ts`
    * `api/src/application/post/post.model.test.ts`

    터미널에서 다음 명령어로도 실행 가능합니다:

    ```bash theme={null}
    cd api
    pnpm sonamu scaffold model Post
    pnpm sonamu scaffold model_test Post
    ```
  </Step>
</Steps>

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

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

<Frame caption="VS Code에서 열린 post.model.ts 파일">
  <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/afterScaffolding-model.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=e5f22f8d0aa05689cedd3463744c7261" alt="VS Code에서 열린 post.model.ts 파일" style={{ width: "100%", borderRadius: "0.75rem" }} width="2184" height="2458" data-path="images/afterScaffolding-model.png" />
</Frame>

<Info>
  생성된 Model 파일은 기본적인 CRUD API를 포함하고 있습니다. 필요에 따라
  비즈니스 로직을 추가하거나 수정할 수 있습니다.
</Info>

API 서버가 HMR(Hot Module Replacement)로 자동 재시작됩니다:

<Frame caption="터미널에서 HMR 재시작 로그">
  <img src="https://mintcdn.com/cartanova-7788888c/QSwN67OqJqlfz1mQ/images/sonamuUI-scaffolding4.png?fit=max&auto=format&n=QSwN67OqJqlfz1mQ&q=85&s=ee64d4198e8e5c1bca78a117d2f6bda9" alt="터미널에서 HMR 재시작 로그" style={{ width: "100%", borderRadius: "0.75rem" }} width="1906" height="1102" data-path="images/sonamuUI-scaffolding4.png" />
</Frame>

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

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

<Frame caption="📸 필요: VS Code에서 web/src/services/PostService.ts 파일이 생성된 화면" />

```typescript title="web/src/services/PostService.ts (자동 생성)" theme={null}
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로 테스트

<CodeGroup>
  ```bash title="게시글 생성 (POST)" theme={null}
  curl -X POST http://localhost:34900/api/posts \
    -H "Content-Type: application/json" \
    -d '{
      "title": "첫 번째 게시글",
      "content": "Sonamu로 만든 첫 번째 게시글입니다.",
      "author": "홍길동"
    }'
  ```

  ```bash title="게시글 목록 조회 (GET)" theme={null}
  curl http://localhost:34900/api/posts?subset=A
  ```

  ```bash title="게시글 상세 조회 (GET)" theme={null}
  curl http://localhost:34900/api/posts/1?subset=A
  ```

  ```bash title="게시글 삭제 (DELETE)" theme={null}
  curl -X DELETE http://localhost:34900/api/posts/1
  ```
</CodeGroup>

### 브라우저에서 테스트

[http://localhost:34900/api/posts?subset=A](http://localhost:34900/api/posts?subset=A) 로 접속하여 JSON 응답을 확인합니다.

<Frame caption="📸 필요: 브라우저에서 API 응답 JSON 화면 (게시글 목록 데이터)" />

## 완성! 🎉

축하합니다! 첫 번째 엔티티를 성공적으로 만들었습니다.

지금까지 다음 작업을 완료했습니다:

* ✅ Sonamu UI로 엔티티 정의
* ✅ 데이터베이스 테이블 생성
* ✅ 타입 및 스키마 자동 생성
* ✅ 스캐폴딩으로 Model 및 Test 파일 자동 생성
* ✅ REST API 자동 생성
* ✅ 프론트엔드 Service 자동 생성
* ✅ API 테스트

<Warning>
  **스캐폴딩의 장점**

  스캐폴딩을 사용하면:

  * Model 파일을 직접 작성할 필요 없이 기본 CRUD API가 자동 생성됩니다
  * 테스트 파일 템플릿도 함께 생성되어 테스트 작성이 쉬워집니다
  * 프로젝트의 일관된 코드 스타일을 유지할 수 있습니다
  * 생성된 코드는 언제든지 수정하여 비즈니스 로직을 추가할 수 있습니다
</Warning>

## 다음 단계

<CardGroup cols={2}>
  <Card title="개발 워크플로우" icon="diagram-project" href="/ko/getting-started/development-workflow">
    Sonamu의 전체 개발 워크플로우를 이해하고 효율적으로 작업하는 방법을
    배워보세요.
  </Card>

  <Card title="Relation 정의하기" icon="link" href="/ko/core-concepts/entity/relations">
    엔티티 간의 관계를 정의하고 JOIN 쿼리를 작성하는 방법을 알아보세요.
  </Card>

  <Card title="Puri 쿼리 빌더" icon="database" href="/ko/database/puri/basic-queries">
    타입 안전한 쿼리 빌더로 복잡한 데이터베이스 쿼리를 작성하는 방법을
    배워보세요.
  </Card>

  <Card title="테스트 작성하기" icon="flask" href="/ko/testing/writing-tests/test-structure">
    자동 생성된 테스트 템플릿을 기반으로 Model의 비즈니스 로직을 테스트하는
    방법을 알아보세요.
  </Card>
</CardGroup>
