Frame은 데이터베이스 Entity와 연결되지 않은 독립적인 API 엔드포인트를 만들 때 사용합니다.
Frame 개요
Entity 불필요 DB 테이블 없이 작동 자유로운 API 구현
간단한 구조 BaseFrameClass 상속 @api 데코레이터 사용
다양한 용도 헬스체크, 유틸리티 외부 API 프록시
독립적 로직 비즈니스 로직만 구현 Entity 제약 없음
Model vs Frame
Model (BaseModelClass)
// Entity 기반 API
class UserModel extends BaseModelClass {
modelName = "User" ; // ← Entity와 연결
@ api ({ httpMethod: "GET" })
async list () : Promise < User []> {
// users 테이블 조회
const rdb = this . getPuri ( "r" );
return rdb . table ( "users" ). select ( "*" );
}
@ api ({ httpMethod: "POST" })
async create ( params : UserSaveParams ) : Promise <{ userId : number }> {
// users 테이블에 삽입
const wdb = this . getPuri ( "w" );
wdb . ubRegister ( "users" , params );
const [ userId ] = await wdb . ubUpsert ( "users" );
return { userId };
}
}
Model의 특징 :
Entity(DB 테이블)와 1:1 매핑
CRUD 작업 중심
UpsertBuilder, Subset 활용
타입 안전성 보장
Frame (BaseFrameClass)
// Entity 없는 독립적 API
class HealthFrame extends BaseFrameClass {
frameName = "Health" ; // ← Entity와 무관
@ api ({ httpMethod: "GET" })
async check () : Promise <{
status : "ok" | "error" ;
timestamp : Date ;
uptime : number ;
}> {
// DB 접근 없이 서버 상태만 반환
return {
status: "ok" ,
timestamp: new Date (),
uptime: process . uptime (),
};
}
@ api ({ httpMethod: "GET" })
async ping () : Promise <{ pong : string }> {
return { pong: "pong" };
}
}
Frame의 특징 :
Entity와 무관한 독립적 로직
유틸리티, 헬스체크, 프록시 등
자유로운 API 구조
간단한 구현
비교 표
특징 Model Frame Entity 연결 ✅ 필수 ❌ 불필요 DB 접근 CRUD 중심 선택적 사용 사례 데이터 관리 유틸리티, 프록시 복잡도 높음 낮음 UpsertBuilder ✅ 사용 ❌ 불필요 Subset ✅ 사용 ❌ 불필요
Frame 사용 시기
✅ Frame을 사용해야 하는 경우
헬스체크 API
// GET /api/health/check
// Entity 없이 서버 상태만 확인
유틸리티 API
// POST /api/utils/hash
// 문자열 해시 계산 등 단순 기능
외부 API 프록시
// GET /api/weather/current
// 외부 날씨 API를 내부 API로 래핑
집계 통계
// GET /api/stats/dashboard
// 여러 테이블의 통계를 집계
인증/권한 체크
// POST /api/auth/verify-token
// JWT 토큰 검증만 수행
❌ Model을 사용해야 하는 경우
Entity CRUD 작업
// User, Product, Order 등의 생성/조회/수정/삭제
관계 데이터 처리
// User → Profile, Order → OrderItems
복잡한 비즈니스 로직
// 주문 처리, 결제 등 여러 Entity 조작
구조 차이
Model 구조
api/src/application/
user/
├── user.entity.json # ← Entity 정의
├── user.model.ts # ← Model 클래스
├── user.types.ts # ← 타입 정의
└── subsets/
└── user-list.subset.json
Frame 구조
api/src/frames/
health/
└── health.frame.ts # ← Frame 클래스만
utils/
└── utils.frame.ts
Frame은 Entity 정의 없이 .frame.ts 파일만 있으면 됩니다.
간단한 예제
헬스체크 Frame
class HealthFrame extends BaseFrameClass {
frameName = "Health" ;
@ api ({ httpMethod: "GET" })
async check () : Promise <{
status : "ok" | "error" ;
timestamp : Date ;
database : "connected" | "disconnected" ;
}> {
// DB 연결 체크
let dbStatus : "connected" | "disconnected" = "disconnected" ;
try {
const rdb = this . getPuri ( "r" );
await rdb . raw ( "SELECT 1" );
dbStatus = "connected" ;
} catch ( error ) {
console . error ( "Database connection failed:" , error );
}
return {
status: dbStatus === "connected" ? "ok" : "error" ,
timestamp: new Date (),
database: dbStatus ,
};
}
@ api ({ httpMethod: "GET" })
async ping () : Promise <{ message : string }> {
return { message: "pong" };
}
}
// 엔드포인트:
// GET /api/health/check
// GET /api/health/ping
유틸리티 Frame
import crypto from "crypto" ;
class UtilsFrame extends BaseFrameClass {
frameName = "Utils" ;
@ api ({ httpMethod: "POST" })
async hash ( params : {
text : string ;
algorithm : "md5" | "sha256" | "sha512" ;
}) : Promise <{ hash : string }> {
const hash = crypto
. createHash ( params . algorithm )
. update ( params . text )
. digest ( "hex" );
return { hash };
}
@ api ({ httpMethod: "POST" })
async uuid () : Promise <{ uuid : string }> {
return { uuid: crypto . randomUUID () };
}
}
// 엔드포인트:
// POST /api/utils/hash
// POST /api/utils/uuid
장단점
독립성 DB 스키마 독립적 외부 API 연동 용이
타입 안전성 감소 Entity 타입 미사용 수동 타입 관리
자동 생성 미지원 Subset, Types 자동 생성 없음 수동 작성 필요
실전 팁
1. Frame은 간단하게 유지
// ✅ 좋음: 단순한 로직
class HealthFrame extends BaseFrameClass {
@ api ({ httpMethod: "GET" })
async check () : Promise <{ status : string }> {
return { status: "ok" };
}
}
// ❌ 나쁨: 복잡한 비즈니스 로직
class OrderFrame extends BaseFrameClass {
@ api ({ httpMethod: "POST" })
async process ( params : ComplexOrderParams ) : Promise < OrderResult > {
// 100줄의 복잡한 로직...
// 이런 경우는 Model로 만들어야 함
}
}
2. 적절한 네이밍
// ✅ 좋음: 명확한 이름
class HealthFrame extends BaseFrameClass {
frameName = "Health" ;
}
class UtilsFrame extends BaseFrameClass {
frameName = "Utils" ;
}
// ❌ 나쁨: 모호한 이름
class MiscFrame extends BaseFrameClass {
frameName = "Misc" ;
}
3. 관련 기능 그룹화
// ✅ 좋음: 관련된 기능을 하나의 Frame에
class CryptoUtilsFrame extends BaseFrameClass {
frameName = "CryptoUtils" ;
@ api ({ httpMethod: "POST" })
async hash ( params : HashParams ) : Promise <{ hash : string }> { /* ... */ }
@ api ({ httpMethod: "POST" })
async encrypt ( params : EncryptParams ) : Promise <{ encrypted : string }> { /* ... */ }
@ api ({ httpMethod: "POST" })
async decrypt ( params : DecryptParams ) : Promise <{ decrypted : string }> { /* ... */ }
}
언제 Model로 전환할까?
Frame으로 시작했지만 다음 상황이 되면 Model로 전환 고려:
DB 데이터를 자주 조회/수정
타입 안전성이 중요해짐
복잡한 데이터 구조가 생김
Entity 타입 자동 생성이 필요
관계 데이터 처리 필요
여러 테이블 간의 관계 처리
JOIN, 외래키 등
비즈니스 로직이 복잡해짐
다음 단계