개발 노트/Backend,CMS,API

[Backend/CMS] 프로젝트에서 관리자 화면과 앱 API를 분리하는 기준

pposooj 2026. 5. 20. 14:41

Backend/CMS 프로젝트를 만들다 보면 관리자 화면과 모바일 앱 API가 같은 데이터를 다루는 경우가 많습니다. 예를 들어 공지사항, 관광지 정보, 배너, 카테고리, 사용자 문의 같은 데이터는 관리자 CMS에서 등록하고 앱에서는 API로 조회하게 됩니다.

처음에는 같은 controller나 같은 query를 재사용하고 싶어질 수 있습니다. 그렇지만 관리자 화면과 앱 API는 목적이 다릅니다. 관리자는 수정, 삭제, 내부 상태 확인이 필요하고, 앱은 공개 가능한 데이터만 안정적인 응답 형태로 받아야 합니다.

이 차이를 분리하지 않으면 내부 필드가 앱에 노출되거나, 관리자 인증 기준이 API와 섞이는 문제가 생길 수 있습니다. 처음에는 같은 코드를 재사용하는 쪽이 빠르게 느껴지지만, 실제로 정리해보면 관리자 화면과 앱 API는 요청과 응답의 책임을 나누는 편이 더 안정적입니다.

이 글에서는 Backend/CMS 프로젝트에서 관리자 화면과 앱 API를 분리하는 기준을 정리해보겠습니다. 백엔드와 CMS 구조를 검토할 때 놓치기 쉬운 route, controller, 인증, 응답 모델, 공개 조건을 중심으로 살펴보겠습니다.

이 글에서 다룰 문제

관리자 화면과 앱 API를 제대로 분리하지 않으면 다음 문제가 생길 수 있습니다.

  • 앱 API 응답에 관리자용 내부 필드가 노출된다.
  • 관리자 검색 조건과 앱 공개 조건이 섞인다.
  • 앱에서 필요 없는 삭제 여부, 검수 상태, 작성자 ID가 그대로 내려간다.
  • 관리자 인증과 앱 사용자 인증 흐름이 모호해진다.
  • API 응답 형식이 화면 테이블 구조에 맞춰져 앱에서 사용하기 어렵다.
  • CMS 개편이나 Next.js 전환 시 API 계약이 함께 흔들린다.

처음에는 한 controller 안에 분기 조건을 넣는 방식이 빠르게 느껴질 수 있습니다. 하지만 기능이 늘어나면 관리자와 앱의 책임을 분리하는 쪽이 유지보수에 훨씬 안전합니다. 특히 앱 API는 외부에 공개되는 계약이기 때문에, 관리자 화면과 같은 기준으로 다루면 나중에 수정하기가 더 어려워질 수 있습니다.

관리자 화면과 앱 API의 차이

관리자 화면과 앱 API는 같은 데이터를 보더라도 사용 목적이 다릅니다. 이 차이를 먼저 인정해야 구조를 정리하기 쉬워집니다.

구분 관리자 CMS 앱 API
사용자 운영자, 관리자 앱 사용자
목적 데이터 등록, 수정, 검수, 삭제 공개 데이터 조회
인증 관리자 세션, 관리자 권한 앱 토큰, 공개 API, 사용자 인증
응답 형태 테이블, 검색, 페이지네이션 앱 화면용 DTO/JSON
필드 범위 내부 관리 필드 포함 가능 공개 가능한 필드만 포함
오류 처리 관리자 화면 메시지 앱에서 처리 가능한 코드와 메시지

관리자 화면은 운영 효율이 중요하고, 앱 API는 응답 계약과 공개 범위가 중요합니다. 이 기준을 나눠두면 어떤 코드를 재사용해도 되는지, 어떤 코드는 분리해야 하는지 조금 더 명확해지는 것 같습니다.

라우팅부터 분리하기

가장 먼저 분리할 것은 route입니다. route가 섞이면 인증 middleware, 로그, rate limit, 응답 포맷까지 같이 섞이기 쉽습니다.

/admin/notices            관리자 공지 목록 화면
/admin/notices/{id}/edit  관리자 공지 수정 화면
/api/v1/notices           앱 공지 목록 API
/api/v1/notices/{id}      앱 공지 상세 API

Laravel 기준으로 보면 관리자 route는 web.php, 앱 API는 api.php 또는 별도 API route 파일에 두는 방식으로 나눠볼 수 있습니다.

// routes/web.php
Route::middleware(['web', 'auth:admin'])
    ->prefix('admin')
    ->group(function () {
        Route::get('/notices', [AdminNoticeController::class, 'index']);
    });

// routes/api.php
Route::middleware(['api'])
    ->prefix('v1')
    ->group(function () {
        Route::get('/notices', [NoticeApiController::class, 'index']);
    });

핵심은 관리자 controller와 API controller를 분리하는 것입니다. 내부 service나 repository는 재사용할 수 있지만, 요청과 응답을 처리하는 책임은 나누는 편이 좋습니다.

처음에는 같은 controller에서 분기하면 코드가 줄어드는 것처럼 보일 수 있습니다. 하지만 관리자 화면과 앱 API가 요구하는 인증, 응답 포맷, 오류 처리가 달라지기 시작하면 하나의 controller가 금방 복잡해질 수 있습니다.

응답 모델은 DTO나 Resource로 분리하기

관리자 화면에서는 내부 상태가 필요할 수 있습니다. 하지만 앱 API에는 공개 가능한 필드만 내려야 합니다. 이때 DTO나 Resource를 사용하면 응답 필드를 명확하게 제한할 수 있습니다.

final class NoticeApiResource
{
    public static function fromModel(object $notice): array
    {
        return [
            'id' => (int) $notice->id,
            'title' => (string) $notice->title,
            'summary' => (string) ($notice->summary ?? ''),
            'publishedAt' => optional($notice->published_at)->toIso8601String(),
        ];
    }
}

관리자 화면에서 쓰는 모델 필드를 그대로 반환하면 내부 필드가 노출될 수 있습니다. 예를 들어 created_by, updated_by, is_deleted, admin_memo, review_status, internal_code 같은 값은 앱에 필요하지 않을 가능성이 큽니다.

처음에는 모델 전체를 그대로 JSON으로 내려보내는 방식이 편해 보일 수 있습니다. 하지만 앱 API는 공개 범위가 중요하기 때문에, 필요한 필드만 골라서 내려주는 구조가 더 안전합니다.

관리자 검색 조건과 앱 공개 조건 분리하기

관리자 화면은 숨김, 비공개, 삭제 예정, 검수 대기 데이터까지 검색해야 할 수 있습니다. 반면 앱 API는 공개 가능한 데이터만 내려야 합니다.

final class NoticeQuery
{
    public function forAdmin(array $filters): Builder
    {
        return Notice::query()
            ->when($filters['keyword'] ?? null, function ($query, $keyword) {
                $query->where('title', 'like', "%{$keyword}%");
            })
            ->orderByDesc('id');
    }

    public function forApp(): Builder
    {
        return Notice::query()
            ->where('is_published', true)
            ->whereNull('deleted_at')
            ->where('published_at', '<=', now())
            ->orderByDesc('published_at');
    }
}

중요한 것은 앱 공개 조건을 controller마다 반복하지 않는 것입니다. forApp() 같은 기준을 두면 공개 조건이 누락될 가능성을 줄일 수 있습니다.

처음에는 controller 안에서 필요한 조건을 바로 추가하는 방식이 간단해 보입니다. 하지만 앱 API가 늘어나면 공개 조건이 화면마다 조금씩 달라질 수 있습니다. 공개 데이터 기준은 query scope나 service에 모아두는 편이 더 관리하기 좋습니다.

인증과 권한 기준 분리하기

관리자 인증과 앱 API 인증은 목적이 다릅니다. 관리자 화면은 운영자 계정을 기준으로 하고, 앱 API는 앱 사용자나 공개 클라이언트를 기준으로 동작합니다.

항목 관리자 인증 앱 API 인증
대상 운영자 계정 앱 사용자 또는 공개 클라이언트
방식 세션, 관리자 guard 토큰, API key, public endpoint
권한 등록/수정/삭제/검수 조회, 사용자별 데이터 접근
실패 응답 로그인 페이지 또는 관리자 오류 JSON 오류 코드

관리자 인증 실패가 앱 API에서 HTML 로그인 페이지로 내려가면 앱에서는 처리하기 어렵습니다. API는 항상 JSON으로 오류를 반환하도록 분리하는 것이 좋습니다.

이 부분은 실제로 한 번 막히기 쉬운 지점입니다. 브라우저에서는 로그인 페이지로 이동하는 흐름이 자연스럽지만, 모바일 앱에서는 HTML 응답을 받으면 오류 처리 기준이 흐려질 수 있습니다.

앱 API 응답 계약 문서화하기

앱 API는 화면과 직접 연결되기 때문에 응답 계약을 문서화하는 편이 좋습니다. 응답 필드, 페이지네이션 구조, 오류 코드가 정해져 있으면 앱과 CMS를 더 안정적으로 분리할 수 있습니다.

{
  "data": [
    {
      "id": 1,
      "title": "공지 제목",
      "summary": "공지 요약",
      "publishedAt": "2026-05-13T00:00:00+09:00"
    }
  ],
  "meta": {
    "page": 1,
    "limit": 20,
    "hasNext": false
  }
}

응답 계약이 정해져 있으면 CMS 화면을 개편하더라도 앱 API는 안정적으로 유지할 수 있습니다. 관리자 화면 테이블 컬럼이 바뀐다고 앱 API 필드가 함께 바뀌면 안 됩니다.

분리 기준 체크리스트

관리자 화면과 앱 API를 분리할 때는 아래 항목을 먼저 확인해보면 좋습니다. 가장 중요한 기준은 앱 API가 공개 가능한 필드만 반환하는지입니다.

  • 관리자 route와 API route가 분리되어 있는지 확인한다.
  • 관리자 controller와 API controller의 책임이 분리되어 있는지 확인한다.
  • 앱 API는 DTO/Resource를 통해 공개 필드만 반환하는지 확인한다.
  • 앱 공개 조건이 query scope 또는 service로 분리되어 있는지 확인한다.
  • 관리자 인증 실패와 API 인증 실패 응답이 다르게 처리되는지 확인한다.
  • API 응답 계약이 문서화되어 있는지 확인한다.
  • 내부 필드, 관리자 메모, 삭제 상태, 작성자 ID가 앱에 노출되지 않는지 확인한다.

앱에 필요 없는 내부 값은 내려보내지 않는 것이 기본입니다. 처음에는 필드가 많아도 괜찮아 보일 수 있지만, 한번 공개된 API 응답은 나중에 줄이기 어렵습니다. 처음부터 공개 범위를 좁게 잡는 것이 안전한 것 같습니다.

자주 하는 실수

1. 관리자 목록 API를 그대로 앱에서 사용하는 경우가 있습니다. 관리자 목록은 내부 상태와 검색 조건이 많기 때문에 앱 공개 API와 목적이 다릅니다.

2. 모델 전체를 JSON으로 반환하는 경우도 있습니다. 편하지만 내부 필드가 노출될 가능성이 큽니다. 앱 API는 DTO나 Resource로 응답 형태를 고정하는 편이 좋습니다.

3. 인증 실패 응답이 HTML로 내려가는 경우가 있습니다. 앱 API에서는 JSON 오류 응답이 내려와야 앱에서 안정적으로 처리할 수 있습니다.

4. CMS 화면 개편과 API 변경을 함께 처리하는 경우도 있습니다. 관리자 UI는 바뀌어도 앱 API 계약은 가능한 한 안정적으로 유지해야 합니다.

결론

Backend/CMS 프로젝트에서 관리자 화면과 앱 API는 같은 데이터를 다루더라도 책임이 다릅니다. 관리자는 운영과 편집을 위한 화면이고, 앱 API는 공개 가능한 데이터를 안정적인 계약으로 제공하는 인터페이스입니다.

좋은 구조는 route, controller, 인증, 응답 모델, 공개 조건을 분리하는 것입니다. 내부 service나 query 일부는 재사용할 수 있지만, 앱 API 응답은 DTO나 Resource로 별도 관리해야 합니다.

처음에는 같은 controller를 재사용하는 방식이 빠르게 느껴질 수 있습니다. 하지만 앱이 출시되고 CMS가 계속 바뀌기 시작하면 분리된 API 계약이 훨씬 중요해집니다. 특히 내부 필드가 앱에 노출되지 않도록 공개 범위를 명확히 하는 것이 가장 기본적인 기준입니다.

참고 자료

  • Laravel Routing, Middleware, API Resource 공식 문서
  • REST API 응답 설계와 pagination 관련 문서
  • 프로젝트 내부 CMS route, API route, DTO/Resource 문서
  • 개인정보와 내부 필드 노출 방지 체크리스트