티스토리 뷰

반응형

백엔드 프로젝트는 어떻게 시작해야 할까? FastAPI · Spring Boot · Node.js로 뼈대부터 잡는 첫 글

백엔드 글을 처음 시리즈로 쓰려고 마음먹고 제일 먼저 든 생각이 있었어요.
“기술 스택 비교부터 할까?”
근데 실무에서는 그 순서가 은근히 함정이더라고요.

FastAPI가 빠르다, Spring Boot가 안정적이다, Node.js가 생산성이 좋다… 다 맞는 말인데요.
막상 프로젝트를 시작할 때 진짜 중요한 건 프레임워크 자체보다 프로젝트를 어떤 구조로 시작하느냐였습니다.

저도 예전에 이걸 너무 가볍게 보고 시작했다가, 초반엔 빨리 개발한 것 같았는데 조금만 기능이 늘어나도

  • 라우터 위치가 뒤죽박죽이고
  • DTO인지 엔티티인지 섞이고
  • 비즈니스 로직이 컨트롤러에 들어가고
  • 테스트는 손도 못 대는 상태가 되곤 했어요.

그래서 이 시리즈의 첫 글은 일부러 화려한 기능보다 프로젝트 구성부터 갑니다.
그리고 같은 기준으로 FastAPI / Spring Boot / Node.js(Express) 세 가지를 나란히 놓고,
“백엔드가 처음 기능을 붙일 때 어떤 모양으로 출발하면 덜 망가지는지”를 보여드릴게요.

공식 문서 기준으로도 FastAPI는 타입 힌트를 기반으로 한 API 개발을 강조하고, APIRouter를 통해 큰 애플리케이션을 여러 파일로 나누는 방식을 안내합니다. Spring Boot는 독립 실행형 프로덕션급 애플리케이션을 빠르게 만들 수 있도록 설계되어 있고, Node.js는 서버와 CLI 등 다양한 런타임 용도로 사용되며, Express 5 계열은 현재 npm 기본 버전으로 안내되고 있습니다. (FastAPI)


이번 글에서 만들 기준

이번 글에서는 복잡한 인증이나 DB 연동까지 바로 가지 않습니다.
대신 앞으로 시리즈 전체에서 공통으로 쓸 수 있는 최소 구조를 먼저 고정하겠습니다.

우리가 지금 만들 기준은 이거예요.

  1. 라우터/컨트롤러는 요청과 응답만 담당
  2. 서비스는 비즈니스 로직 담당
  3. 도메인 또는 모델은 데이터 구조 담당
  4. 설정은 분리
  5. 헬스 체크 API부터 만든다
  6. 나중에 DB, 인증, 로그, 예외처리를 붙이기 쉬워야 한다

저는 이 기준이 정말 중요하다고 봐요.
처음부터 완벽할 필요는 없는데, 나중에 커질 수 있는 방향으로 시작하는 것은 진짜 중요합니다.


백엔드에서 공통적으로 필요한 기능

프레임워크가 달라도 대부분의 백엔드는 결국 비슷한 기능을 갖게 됩니다.

  • 헬스 체크 API
  • 사용자 요청 검증
  • 비즈니스 로직 서비스 계층
  • DB 접근 계층
  • 공통 예외 처리
  • 환경변수/설정 관리
  • 인증/인가
  • 로깅
  • 테스트
  • API 문서화

FastAPI는 기본적으로 OpenAPI 문서 생성을 강하게 지원하고, Spring Boot는 외부 설정과 내장 서버, 운영 기능을 폭넓게 제공하며, Node.js는 런타임 자체가 범용적이어서 Express 같은 웹 프레임워크를 조합해 구조를 만들어 가는 방식이 일반적입니다. (FastAPI)


프로젝트를 시작할 때 추천하는 공통 폴더 구조

아래 구조는 제가 실무에서 제일 무난하다고 느끼는 출발점입니다.

project-root/
├── app-or-src/
│   ├── main / Application
│   ├── api / controller / routes
│   ├── service
│   ├── domain / model / dto
│   ├── repository
│   ├── config
│   └── common
├── tests
├── .env
├── README.md
└── build files

핵심은 간단해요.

  • api(controller/routes): HTTP 요청 진입점
  • service: 실제 업무 규칙 처리
  • domain/model/dto: 요청/응답/도메인 데이터 표현
  • repository: DB 접근
  • config: 환경설정, CORS, 보안, DB 설정
  • common: 예외, 응답 포맷, 유틸

이렇게만 해도 프로젝트가 커질 때 숨통이 좀 트입니다.
특히 “컨트롤러에서 다 해버리는 습관”을 끊기 좋아요.


1) FastAPI 프로젝트 구성

FastAPI 공식 문서는 FastAPI() 앱을 만들고 경로 함수를 정의하는 가장 단순한 시작점과 함께, 여러 파일로 분리하는 큰 애플리케이션 구조에서 APIRouter를 사용하는 방식을 안내합니다. 최근 릴리스 노트도 계속 갱신되고 있어서 버전 변화가 꽤 빠른 편입니다. (FastAPI)

추천 구조

fastapi-backend/
├── app/
│   ├── main.py
│   ├── api/
│   │   └── health.py
│   ├── services/
│   │   └── health_service.py
│   └── schemas/
│       └── health.py
├── requirements.txt
└── .env

코드

app/schemas/health.py

from pydantic import BaseModel


class HealthResponse(BaseModel):
    status: str
    framework: str

app/services/health_service.py

from app.schemas.health import HealthResponse


class HealthService:
    @staticmethod
    def check() -> HealthResponse:
        return HealthResponse(status="ok", framework="fastapi")

app/api/health.py

from fastapi import APIRouter
from app.schemas.health import HealthResponse
from app.services.health_service import HealthService

router = APIRouter(prefix="/api/health", tags=["health"])


@router.get("", response_model=HealthResponse)
def health_check() -> HealthResponse:
    return HealthService.check()

app/main.py

반응형
from fastapi import FastAPI
from app.api.health import router as health_router

app = FastAPI(title="backend-series-fastapi")

app.include_router(health_router)

requirements.txt

fastapi
uvicorn[standard]
pydantic

실행

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn app.main:app --reload

확인

curl http://127.0.0.1:8000/api/health

예상 응답:

{
  "status": "ok",
  "framework": "fastapi"
}

이 구조에서 중요한 점

FastAPI는 처음엔 파일 하나로도 정말 빨리 시작돼요.
근데 저는 일부러 처음부터 api / services / schemas를 나눕니다.

왜냐면 FastAPI는 너무 빨리 개발돼서, 오히려 나중에 로직이 라우터에 막 스며들기 쉬워요.
초반 30분은 귀찮아도, 한 달 뒤에는 훨씬 편합니다.


2) Spring Boot 프로젝트 구성

Spring 공식 가이드는 Spring Initializr로 웹 프로젝트를 만들고, 작은 Hello World 애플리케이션부터 시작하는 흐름을 제공합니다. 또한 Spring Boot는 독립 실행형, 프로덕션 지향 애플리케이션을 빠르게 만드는 것을 핵심 가치로 두고 있습니다. 현재 Spring Boot 프로젝트 페이지에는 4.0.3이 표시되어 있습니다. (Home)

추천 구조

springboot-backend/
├── src/main/java/com/example/backend/
│   ├── BackendApplication.java
│   ├── controller/
│   │   └── HealthController.java
│   ├── service/
│   │   └── HealthService.java
│   └── dto/
│       └── HealthResponse.java
├── src/main/resources/
│   └── application.yml
└── build.gradle

코드

src/main/java/com/example/backend/BackendApplication.java

package com.example.backend;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class BackendApplication {
    public static void main(String[] args) {
        SpringApplication.run(BackendApplication.class, args);
    }
}

src/main/java/com/example/backend/dto/HealthResponse.java

package com.example.backend.dto;

public record HealthResponse(
        String status,
        String framework
) {
}

src/main/java/com/example/backend/service/HealthService.java

package com.example.backend.service;

import com.example.backend.dto.HealthResponse;
import org.springframework.stereotype.Service;

@Service
public class HealthService {

    public HealthResponse check() {
        return new HealthResponse("ok", "spring-boot");
    }
}

src/main/java/com/example/backend/controller/HealthController.java

package com.example.backend.controller;

import com.example.backend.dto.HealthResponse;
import com.example.backend.service.HealthService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HealthController {

    private final HealthService healthService;

    public HealthController(HealthService healthService) {
        this.healthService = healthService;
    }

    @GetMapping("/api/health")
    public HealthResponse healthCheck() {
        return healthService.check();
    }
}

src/main/resources/application.yml

spring:
  application:
    name: backend-series-spring
server:
  port: 8080

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '4.0.3'
    id 'io.spring.dependency-management' version '1.1.7'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(21)
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

실행

./gradlew bootRun

확인

curl http://127.0.0.1:8080/api/health

예상 응답:

{
  "status": "ok",
  "framework": "spring-boot"
}

이 구조에서 중요한 점

Spring Boot는 솔직히 초반 진입 장벽이 있습니다.
패키지 구조도 신경 써야 하고, 의존성 주입도 이해해야 하고, 설정도 많죠.

근데 그 대신 서비스가 커질수록 버티는 힘이 있어요.
처음부터 controller / service / dto를 나누면, 이후에 JPA, Security, Validation 붙일 때 훨씬 자연스럽게 확장됩니다.


3) Node.js 프로젝트 구성

Node.js 공식 문서는 서버, 웹 앱, CLI 등 다양한 용도로 쓰이는 런타임이라고 설명합니다. Express는 2025년 3월 기준 5.1.0이 npm 기본 버전으로 안내되었고, 보안 공지에서도 최신 버전 유지의 중요성을 계속 강조하고 있습니다. (Node.js)

여기서는 실무에서 가장 많이 접하게 되는 Node.js + Express 기준으로 갑니다.

추천 구조

node-backend/
├── src/
│   ├── server.js
│   ├── routes/
│   │   └── health.route.js
│   ├── services/
│   │   └── health.service.js
│   └── dto/
│       └── health-response.js
├── package.json
└── .env

코드

src/dto/health-response.js

export function createHealthResponse() {
  return {
    status: "ok",
    framework: "nodejs-express",
  };
}

src/services/health.service.js

import { createHealthResponse } from "../dto/health-response.js";

export function checkHealth() {
  return createHealthResponse();
}

src/routes/health.route.js

import { Router } from "express";
import { checkHealth } from "../services/health.service.js";

const router = Router();

router.get("/api/health", (req, res) => {
  res.json(checkHealth());
});

export default router;

src/server.js

import express from "express";
import healthRouter from "./routes/health.route.js";

const app = express();
const PORT = 3000;

app.use(express.json());
app.use(healthRouter);

app.listen(PORT, () => {
  console.log(`server running on http://localhost:${PORT}`);
});

package.json

{
  "name": "backend-series-node",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "node src/server.js"
  },
  "dependencies": {
    "express": "^5.1.0"
  }
}

실행

npm install
npm run dev

확인

curl http://127.0.0.1:3000/api/health

예상 응답:

{
  "status": "ok",
  "framework": "nodejs-express"
}

이 구조에서 중요한 점

Node.js는 정말 금방 시작됩니다.
좋게 말하면 유연하고, 나쁘게 말하면 아무렇게나 커지기 쉽다는 뜻이기도 해요.

그래서 저는 Node.js일수록 더 빨리 구조를 나눕니다.

  • route
  • service
  • dto
  • config
  • middleware

이 정도만 나눠도 나중에 인증, 에러핸들링, DB 연결 붙일 때 훨씬 덜 엉킵니다.


세 가지를 처음 시작할 때 어떻게 고르면 될까?

이 질문을 진짜 많이 받아요.
제 경험상 이렇게 보면 좀 편합니다.

FastAPI가 잘 맞는 경우

  • Python 생태계를 이미 쓰고 있다
  • AI, 데이터 처리, 자동화와 연결된다
  • 빠르게 API를 만들고 싶다
  • 타입 기반 요청/응답 검증이 좋다

FastAPI는 공식적으로 타입 힌트와 자동 문서화의 강점을 전면에 내세웁니다. (FastAPI)

Spring Boot가 잘 맞는 경우

  • 기업형 서비스
  • 복잡한 인증/권한/트랜잭션
  • 장기 운영
  • 큰 팀 협업

Spring Boot는 외부 설정, 내장 서버, 메트릭, 헬스체크 같은 비기능 요구사항까지 넓게 다루는 방향을 문서에서 분명히 보여줍니다. (Home)

Node.js가 잘 맞는 경우

  • 프론트와 언어 통일을 원한다
  • 빠른 MVP가 필요하다
  • 실시간 처리나 가벼운 API 서버를 빠르게 만들고 싶다

Node.js는 범용 런타임이고, Express는 얇고 단순한 웹 계층을 제공해서 빠르게 조립하기 좋습니다. (Node.js)


저는 왜 “헬스 체크 API”부터 만들라고 할까?

이건 좀 사소해 보여도 실무에서는 꽤 중요합니다.

헬스 체크 API 하나만 있어도 바로 확인 가능한 게 많아요.

  • 서버가 뜨는지
  • 라우팅이 정상인지
  • 배포 후 살아 있는지
  • 리버스 프록시 연결이 되는지
  • 컨테이너 오케스트레이션에서 상태 체크가 되는지

즉, GET /api/health는 그냥 연습용 엔드포인트가 아니라
나중에 운영 환경까지 이어지는 첫 번째 운영 API에 가깝습니다.


지금 단계에서 일부러 하지 않은 것

이번 글에서는 일부러 아래를 넣지 않았습니다.

  • DB 연결
  • 예외 처리 전역화
  • 인증/인가
  • 로그 포맷 통일
  • 환경별 설정 분리
  • 테스트 코드

왜냐면 첫 글에서 다 넣으면, 구조를 이해하기 전에 설정 지옥부터 오기 때문이에요.
순서가 중요합니다.

지금은 “코드가 어디에 있어야 하는지”를 먼저 익히는 단계가 맞아요.


이번 글 핵심 정리

프로젝트 시작할 때 제일 먼저 정해야 하는 건 프레임워크보다도 구조입니다.

오늘 만든 공통 감각은 이겁니다.

  • 컨트롤러/라우터는 얇게
  • 서비스에 로직 배치
  • DTO/스키마 분리
  • 설정 분리
  • 헬스 체크 API부터 시작

그리고 이 패턴은
FastAPI든, Spring Boot든, Node.js든
생각보다 오래 갑니다. 진짜로요.


다음 글 예고

다음 글에서는 이 구조를 이어서
“환경변수, 설정 파일, 실행 환경(dev/prod) 분리”를 다뤄보겠습니다.

이 단계가 들어가면 이제 슬슬 “진짜 프로젝트 같다”는 느낌이 나기 시작해요.
그다음부터는 DB, 인증, 예외 처리, 공통 응답 포맷으로 확장해 나가면 됩니다.


출처

  • FastAPI 공식 문서 및 레퍼런스 (FastAPI)
  • FastAPI 릴리스 노트 (FastAPI)
  • Spring Boot 공식 프로젝트 페이지 및 가이드 (Home)
  • Node.js 공식 문서 (Node.js)
  • Express 공식 릴리스/보안 공지 (Express)


FastAPI, SpringBoot, Nodejs, Express, 백엔드개발, 프로젝트구성, API서버구조, 백엔드시리즈, 주니어백엔드, 서버초기세팅

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
글 보관함
반응형