첫 번째 프로덕션 API를 구축했을 때, Express.js를 사용했고 설정에 약 두 시간이 걸렸습니다: 보일러플레이트, 미들웨어, 라우트 핸들러, TypeScript 구성 및 배포 스크립트. 최근에는 NitroJs를 사용하여 완전한 REST API를 단 20분 만에 출시했습니다. app.use() 체인도, 수동 라우팅도, 배포 문제도 없었습니다.
이 가이드는 NitroJs가 어떻게 보일러플레이트를 제거하면서 Node, Bun, Deno, 서버리스 또는 엣지 등 어디에서나 실행되는 프로덕션 준비 백엔드를 제공하는지 보여줍니다.
NitroJs란 무엇이며 백엔드 API에 사용해야 하는 이유는 무엇인가요?
NitroJs는 백엔드를 빌드 아티팩트로 취급하는 UnJS 생태계의 서버 툴킷입니다. 런타임에 결합되는 서버를 작성하는 대신, 라우트 핸들러를 작성하면 Nitro가 이를 휴대 가능한 번들로 컴파일합니다. 코드 한 줄 변경 없이 동일한 코드를 Node.js, Cloudflare Workers, Vercel 또는 Docker 컨테이너에 배포할 수 있습니다.

핵심 사고방식: 서버가 아닌 라우트를 작성하세요. API 로직에 집중하면 Nitro가 HTTP, 미들웨어 및 플랫폼 연결을 처리합니다.
백엔드 API의 주요 장점:
- 제로 설정:
server.js보일러플레이트가 없습니다. - 파일 시스템 라우팅:
routes/users.get.ts가GET /users가 됩니다. - TypeScript 네이티브: 핸들러로부터 자동 추론된 타입
- 범용 배포: 한 번의 빌드로 여러 대상에 배포
- 내장 캐싱: 메모리, Redis 또는 Cloudflare KV에 API 응답 저장
- 스토리지 계층: 임시 데이터를 위한 추상화된 파일 시스템
API 개발자에게 이는 인프라를 변경할 때 더 빠르게 출시하고 리팩토링을 덜 한다는 것을 의미합니다.
시작하기: 5분 만에 NitroJs 설치
단계 1: 프로젝트 스캐폴드
# Using pnpm (recommended)
pnpm dlx giget@latest nitro nitro-api-backend
# Or npm
npm create nitro@latest nitro-api-backend
# Or bun
bunx giget@latest nitro nitro-api-backend
이는 공식 스타터 템플릿을 nitro-api-backend로 복제합니다.
단계 2: 구조 탐색
cd nitro-api-backend
tree -L 2
출력:
├── server
│ ├── api
│ ├── routes
│ ├── tasks
│ └── middleware
├── nitro.config.ts
├── package.json
└── tsconfig.json
핵심 디렉토리:
server/api/: API 라우트 (UI 없음)server/routes/: 풀스택 라우트 (SSR용)server/middleware/: 전역 미들웨어server/tasks/: 백그라운드 작업
순수한 백엔드 API의 경우, routes를 무시하고 모든 것을 api에 넣으세요.
단계 3: 개발 서버 실행
pnpm install
pnpm dev
Nitro는 http://localhost:3000에서 핫 리로드와 함께 시작됩니다. 파일을 편집하면 서버가 200ms 이내에 다시 시작됩니다.

첫 번째 API 엔드포인트 구축
간단한 GET 핸들러
server/api/users.get.ts를 생성하세요:
// server/api/users.get.ts
export default defineEventHandler(() => {
return [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" }
];
});
Nitro는 이를 GET /api/users에서 자동으로 제공합니다. defineEventHandler 래퍼는 타입 안전성과 요청 컨텍스트에 대한 접근을 제공합니다.
매개변수가 있는 동적 라우트
server/api/users/[id].get.ts를 생성하세요:
// server/api/users/[id].get.ts
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id');
if (!id) {
throw createError({
statusCode: 400,
statusMessage: 'User ID is required'
});
}
return {
id: Number(id),
name: `User ${id}`,
email: `user${id}@example.com`
};
});
GET /api/users/42에 접속하여 { id: 42, name: "User 42", ... }를 확인하세요.
JSON 본문을 사용한 POST 요청
server/api/users.post.ts를 생성하세요:
// server/api/users.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
// Nitro는 JSON을 자동으로 파싱합니다.
const { name, email } = body;
if (!name || !email) {
throw createError({
statusCode: 422,
statusMessage: 'Name and email are required'
});
}
// Simulate database insertion
const newUser = {
id: Math.floor(Math.random() * 1000),
name,
email,
createdAt: new Date().toISOString()
};
return newUser;
});
테스트하기:
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Charlie","email":"charlie@example.com"}'
타입 안전한 응답
Nitro는 반환 값에서 응답 타입을 추론합니다. 엄격한 계약을 위해서는 명시적 타입을 사용하세요:
// server/api/products.get.ts
interface Product {
id: number;
name: string;
price: number;
inStock: boolean;
}
export default defineEventHandler<Product[]>(() => {
return [
{ id: 1, name: "Laptop", price: 1299.99, inStock: true },
{ id: 2, name: "Mouse", price: 29.99, inStock: false }
];
});
이제 이 엔드포인트를 가져오는 클라이언트는 완전한 IntelliSense를 얻게 됩니다.
고급 API 패턴
인증을 위한 미들웨어
server/middleware/auth.ts를 생성하세요:
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
const protectedRoutes = ['/api/admin', '/api/users/me'];
if (protectedRoutes.some(route => event.path.startsWith(route))) {
const token = getHeader(event, 'authorization')?.replace('Bearer ', '');
if (!token || token !== process.env.API_SECRET) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized'
});
}
}
});
미들웨어는 모든 라우트 이전에 실행됩니다. nitro.config.ts에 추가하세요:
// nitro.config.ts
export default defineNitroConfig({
srcDir: 'server',
runtimeConfig: {
apiSecret: process.env.API_SECRET
}
});
API 응답 캐싱
비용이 많이 드는 계산을 60초 동안 캐시하세요:
// server/api/stats.get.ts
export default defineCachedEventHandler(async () => {
const db = useStorage('data');
const users = await db.getItem('users') || [];
return {
totalUsers: users.length,
activeUsers: users.filter(u => u.lastSeen > Date.now() - 86400000).length
};
}, {
maxAge: 60, // seconds
name: 'stats',
getKey: () => 'global-stats'
});
Nitro는 배포 대상에 따라 캐시 무효화 및 저장소(메모리, Redis 또는 Cloudflare KV)를 처리합니다.
파일 업로드 처리
// server/api/upload.post.ts
export default defineEventHandler(async (event) => {
const files = await readMultipartFormData(event);
if (!files?.length) {
throw createError({ statusCode: 400, statusMessage: 'No files uploaded' });
}
const storage = useStorage('uploads');
const file = files[0];
await storage.setItem(file.filename, file.data);
return { success: true, filename: file.filename };
});
테스트하기:
curl -X POST http://localhost:3000/api/upload \
-F "file=@/path/to/image.jpg"
배포: 로컬에서 프로덕션까지
프로덕션용 빌드
pnpm build
Nitro는 플랫폼별 진입점을 가진 .output 디렉토리를 생성합니다.
Node.js에서 실행
node .output/server/index.mjs
node_modules는 필요하지 않습니다. 빌드가 모든 것을 번들합니다.
Cloudflare Workers에 배포
# Install preset
pnpm add -D nitro-preset-cloudflare-workers
# Build with preset
NITRO_PRESET=cloudflare-workers pnpm build
# Deploy
wrangler deploy .output
Vercel (서버리스)에 배포
# Vercel detects Nitro automatically
vercel
또는 nitro.config.ts를 구성하세요:
export default defineNitroConfig({
preset: 'vercel-edge'
});
환경 변수
로컬에서 .env를 생성한 다음, 프로덕션에서는 플랫폼 대시보드를 사용하세요:
# .env
API_SECRET=dev-secret-key
DATABASE_URL=file:./dev.db
핸들러에서 접근하기:
const config = useRuntimeConfig();
const secret = config.apiSecret;
고급 사용 사례
WebSockets 및 서버-센트 이벤트
Nitro는 defineWebSocketHandler를 통해 WebSockets를 지원합니다:
// server/api/ws.ts
export default defineWebSocketHandler({
open(peer) {
console.log('WebSocket connected:', peer);
},
message(peer, message) {
peer.send(`Echo: ${message.text()}`);
},
close(peer, details) {
console.log('WebSocket closed:', details);
}
});
ws://localhost:3000/api/ws에서 접근할 수 있습니다.
백그라운드 작업
server/tasks/cleanup.ts를 생성하세요:
// server/tasks/cleanup.ts
export default defineCronHandler('0 2 * * *', async () => {
const db = useStorage('temp');
const keys = await db.getKeys();
for (const key of keys) {
const metadata = await db.getMeta(key);
if (metadata.expiresAt < Date.now()) {
await db.removeItem(key);
}
}
console.log('Cleanup completed');
});
이것은 매일 새벽 2시에 실행됩니다. 크론을 지원하는 플랫폼(예: Vercel, Netlify)에 배포하세요.
워크플로우 엔진
복잡한 오케스트레이션을 위해 useWorkflow.dev와 통합하세요:
// server/api/workflows/process.ts
import { createWorkflow } from '@useworkflow/sdk';
export default defineEventHandler(async (event) => {
const workflow = createWorkflow('data-pipeline', {
steps: [
{ id: 'fetch', run: 'https://api.example.com/data' },
{ id: 'transform', run: 'transform.js' },
{ id: 'store', run: async (data) => {
const storage = useStorage('results');
await storage.setItem('latest', data);
}}
]
});
await workflow.start();
return { status: 'started' };
});
실제 개발자 시나리오
시나리오 1: 정적 사이트 백엔드
JSON 데이터를 정적 JAMstack 사이트에 제공하는 Nitro API를 Cloudflare Workers에 배포하세요. 무료 티어는 하루 10만 건의 요청을 처리합니다.
시나리오 2: 모바일 앱 API
React Native 앱을 위한 REST 엔드포인트를 구축하세요. Nitro의 내장 CORS 처리를 사용하세요:
// nitro.config.ts
export default defineNitroConfig({
routeRules: {
'/api/**': { cors: true }
}
});
시나리오 3: 마이크로서비스 게이트웨이
마이크로서비스를 집계하는 API 게이트웨이로 Nitro를 실행하세요. $fetch를 사용하여 요청을 프록시하세요:
// server/api/aggregate.get.ts
export default defineEventHandler(async (event) => {
const [users, posts] = await Promise.all([
$fetch('https://users-service.internal/users'),
$fetch('https://posts-service.internal/posts')
]);
return { users, posts };
});
Apidog로 NitroJs API 테스트하기
Nitro는 백엔드를 빠르게 생성하지만, 엔드포인트가 제대로 작동하지 않으면 속도는 아무 의미가 없습니다. 새로운 POST /api/users를 출시할 때, 라우트 정의를 Apidog로 가져오세요. Apidog는 핸들러의 반환 타입을 역설계하고 계약 테스트를 자동으로 생성합니다.

프론트엔드 팀이 좌절하기 전에 CI에서 이를 실행하여 치명적인 변경 사항을 찾아내세요. 무료로 시작할 수 있으며, 이는 빠른 Nitro 개발에 필요한 안전장치입니다.
자주 묻는 질문
1. Nitro가 Express.js를 완전히 대체할 수 있나요?
네. Nitro는 라우팅, 미들웨어, 배포를 더 잘 처리합니다. Express 미들웨어를 Nitro 플러그인으로, 라우트를 server/api/로 이동하여 마이그레이션할 수 있습니다.
2. Nitro는 데이터베이스 연결을 어떻게 처리하나요?
시작 시 풀을 초기화하기 위해 Nitro 플러그인을 사용하세요. 스토리지 계층은 캐싱을 추상화하지만, SQL의 경우 자체 ORM(Prisma, Drizzle)을 사용하세요.
3. Nitro는 프로덕션 준비가 되었나요?
물론입니다. Nuxt는 수백만 개의 프로덕션 사이트에 Nitro를 사용하고 있습니다. 이는 규모 확장을 위해 충분히 검증되었습니다.
4. Nitro를 GraphQL과 함께 사용할 수 있나요?
네. server/api/graphql.post.ts에 GraphQL 핸들러를 추가하세요. 어떤 Node GraphQL 라이브러리든 사용하세요. Nitro는 제한하지 않습니다.
5. Nitro와 Hono의 차이점은 무엇인가요?
Hono는 경량 라우터입니다. Nitro는 빌드, 프리셋, 스토리지를 포함한 완전한 서버 툴킷입니다. 마이크로서비스에는 Hono를, 전체 백엔드에는 Nitro를 사용하세요.
결론
NitroJs는 API를 휴대 가능한 아티팩트로 컴파일하여 백엔드 개발에 대한 생각을 바꿉니다. 보일러플레이트 없이 파일 시스템 라우팅, TypeScript 안전성 및 범용 배포를 제공합니다. 다음 REST API를 Nitro로 구축하고 Apidog로 유효성을 검사하세요. 구성 한 줄로 Node에서 엣지로 마이그레이션할 때 미래의 당신은 고마워할 것입니다.
