devbox-light-server (7). Dockerizing, MVP 마무리

도커라이징 영속성까지 추가했으므로 이제 서버를 하나의 실행 단위로 묶을 차례다. 지금까지는 로컬에서 Node.js를 설치하고, 의존성을 설치하고, TypeScript를 빌드한 다음 서버를 실행했다. 혼자 개발할 때는 큰 문제가 없지만, 이 서버를 다른 환경에서 실행하려면 매번 같은 준비 과정을 반복해야 한다. 도커라이징은 이 과정을 이미지 안에 고정하는 작업이다. 이 프로젝트에서는 Express 서버를 빌드하고, 실행에 필요한 파일만 담은 뒤, .data 디렉터리를 기준으로 데이터를 유지할 수 있도록 구성했다. Dockerfile 작성하기 Dockerfile은 두 단계로 나눴다. FROM node:24-alpine AS build WORKDIR /app COPY package.json package-lock.json ./ RUN npm ci COPY tsconfig.json tsconfig.build.json ./ COPY src ./src RUN npm run build 첫 번째 build 단계에서는 개발 의존성을 포함해서 설치하고 TypeScript를 빌드한다. npm run build를 실행하면 dist 디렉터리가 만들어진다. ...

August 10, 2026

devbox-light-server (6). Json 영속성

MVP의 마지막 기능으로 서버를 재시작해도 데이터가 사라지지 않도록 영속성을 추가했다. 이번 포스트에서는 devbox-light-server에 DB, Storage, Token Storage에서 영속성을 가진 저장 방식을 구현해서 in-memory 대신 사용할 수 있도록 만든다. 프로토타입이었던 dev-data-server에서는 시작부터 SQL 의존성을 가지고 시작했지만, devbox-light-server는 아주 얇은 in-memory 구현만 가지고 있다. MVP 마지막에 영속성을 넣는 이유는 서버를 구현할 때, 특정 구현에 휘둘리지 않기 위해서다. dev-data-server에서 SQL을 빠르게 들여와서, 결국 프로젝트가 SQL 래퍼가 된 느낌이 있다. 당연히 Service 레이어는 Repository의 인터페이스에만 의존하므로 어떤 방법을 쓰든 단순히 구현체만 갈아 끼우고, 의존성 생성, 연결 부분을 제외하고는 어떠한 코드에도 변화가 없어야 한다. ...

August 10, 2026

devbox-light-server (5). Auth

Auth 설계하기 개발용 목서버에서 실제 서비스 수준의 인증 시스템을 구현하지 않고도, 클라이언트에서 사용하는 로그인과 인증 흐름을 테스트할 수 있도록 Auth 기능을 추가했다. 로그인 요청을 보낸다. access token을 발급받는다. Authorization 헤더에 token을 실어 요청한다. 현재 로그인된 유저를 확인한다. 로그아웃으로 token을 무효화한다. JWT, 비밀번호, OAuth, Refresh Token, 보안 같은 기능은 의도적으로 제외했다. 프로덕션 인증 로직을 구현하려는 글이 아니라, 개발용 Mock 서버에서 클라이언트의 로그인, 인증, 로그아웃 흐름을 확인하는 것이 목적이기 때문이다. 최소한의 Auth 구조 첫 단계에서는 DatabaseService를 그대로 사용했다. users 컬렉션을 만들어서 그 레코드 ID를 유저 ID로 사용해 로그인할 수 있도록 했다. ...

August 8, 2026

dev-data-server-light. 4. File Storage

DB 모듈을 마무리하고, 다음으로 구현한 것은 File Storage 모듈이다. DB 모듈과 크게 다르지 않을 것이라고 생각했다. 우선 CRUD 비슷한 메소드를 처리하고, 구조는 DB 모듈에서 충분히 다듬었기 때문이다. 하지만 실제로는 DB보다 인터페이스를 어떻게 정의할 것인지에 대한 고민이 더 많았다. 인터페이스 만들기 처음 작성한 프롬프트는 다음과 같다. Create the initial FileStorage contract in src/storage. Requirements: - Define a FileStorage interface. - The interface is responsible only for storing and retrieving binary file contents. - It should not manage file metadata, ids, ownership, permissions, or database records. - Use Buffer for file contents. - Include only the minimal operations required for file storage: - save(path: string, content: Buffer): Promise<void> - read(path: string): Promise<Buffer> - delete(path: string): Promise<void> - exists(path: string): Promise<boolean> - Add minimal domain errors if necessary. For this step, create only the contract and related types. Do not implement any storage implementation yet. 로컬 파일 시스템을 의식하고 있어서 의심 없이 path라는 이름을 사용했는데, 이 때문에 인터페이스가 구현체를 닮아버리게 되었다. path는 과연 인터페이스를 대표할 수 있는 네이밍인가? ...

July 2, 2026