1. 프로젝트 생성
터미널
# .NET SDK 설치 확인
dotnet --version
# 프로젝트 생성
dotnet new webapi -n my-api --no-https
cd my-api dotnet new webapi는 ASP.NET Core Web API 프로젝트를 생성합니다.
--no-https 옵션으로 로컬 개발 시 HTTPS를 비활성화합니다.
2. 첫 API 작성
Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
var builder = WebApplication.CreateBuilder(args);
// CORS 설정
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.WithOrigins("http://localhost:5173")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
var app = builder.Build();
app.UseCors();
app.MapGet("/health", () => Results.Ok(new { status = "healthy" }));
app.MapGet("/api/hello", (string? name) =>
Results.Ok(new { message = $"Hello, {name ?? "World"}!" }));
app.Run();실행
터미널
dotnet run
# 테스트 (새 터미널에서)
curl http://localhost:5000/health
curl "http://localhost:5000/api/hello?name=Choorai"3. CRUD API 추가
Program.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
// 모델 정의
record Project(Guid Id, string Name, string Description, DateTime CreatedAt);
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors();
var app = builder.Build();
app.UseCors();
// In-memory 저장소
var projects = new List<Project>();
var lockObj = new object();
var api = app.MapGroup("/api/v1/projects");
// 목록 조회
api.MapGet("/", () =>
{
lock (lockObj)
{
return Results.Ok(new { items = projects, total = projects.Count });
}
});
// 단건 조회
api.MapGet("/{id:guid}", (Guid id) =>
{
lock (lockObj)
{
var project = projects.FirstOrDefault(p => p.Id == id);
return project is not null
? Results.Ok(project)
: Results.NotFound(new { error = "not found" });
}
});
// 생성
api.MapPost("/", (CreateProjectRequest req) =>
{
var project = new Project(
Guid.NewGuid(),
req.Name,
req.Description,
DateTime.UtcNow
);
lock (lockObj)
{
projects.Add(project);
}
return Results.Created($"/api/v1/projects/{project.Id}", project);
});
// 수정
api.MapPut("/{id:guid}", (Guid id, UpdateProjectRequest req) =>
{
lock (lockObj)
{
var index = projects.FindIndex(p => p.Id == id);
if (index == -1)
return Results.NotFound(new { error = "not found" });
projects[index] = projects[index] with
{
Name = req.Name,
Description = req.Description
};
return Results.Ok(projects[index]);
}
});
// 삭제
api.MapDelete("/{id:guid}", (Guid id) =>
{
lock (lockObj)
{
var removed = projects.RemoveAll(p => p.Id == id);
return removed > 0
? Results.NoContent()
: Results.NotFound(new { error = "not found" });
}
});
app.Run();
// Request DTOs
record CreateProjectRequest(string Name, string Description);
record UpdateProjectRequest(string Name, string Description);Minimal API와 record 타입
C#의 record 타입은 불변 데이터 객체를 간결하게 정의합니다.
with 표현식으로 특정 필드만 변경한 새 인스턴스를 생성할 수 있습니다.
4. Docker로 배포
Dockerfile
# Build stage
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY *.csproj .
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app
# Runtime stage
FROM mcr.microsoft.com/dotnet/aspnet:8.0-alpine
WORKDIR /app
COPY --from=build /app .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "my-api.dll"]멀티스테이지 빌드로 SDK(~700MB)에서 런타임(~100MB)으로 줄입니다. Alpine 기반 이미지를 사용하여 크기를 최소화합니다.
터미널
# 로컬 테스트
docker build -t my-dotnet-api .
docker run -p 8080:8080 my-dotnet-api
# 이미지 크기 확인 (~100MB)
docker images my-dotnet-api
# Cloud Run 배포
gcloud run deploy my-dotnet-api \
--source . \
--region asia-northeast3 \
--allow-unauthenticated \
--min-instances 0 \
--max-instances 1프레임워크 비교
| 항목 | .NET (Minimal API) | Go (Chi) | FastAPI |
|---|---|---|---|
| 이미지 크기 | ~100MB | ~15MB | ~200MB |
| Cold Start | ~200ms | ~100ms | ~500ms |
| 메모리 사용 | ~40MB | ~20MB | ~80MB |
| 학습 곡선 | 중간 | 중간 | 쉬움 |
| 타입 시스템 | 정적 | 정적 | 동적 |