개발/FastAPI

FastAPI SQLite 연결하기: SQLModel로 DB 저장 API 만들기

notebase 2026. 5. 26. 17:45

FastAPI에서 SQLite를 연결하는 방법을 SQLModel 기준으로 정리합니다. 최신 lifespan 방식으로 테이블을 생성하고, 데이터 저장·조회·삭제 API까지 초보자도 따라 할 수 있게 설명합니다.

 

FastAPI SQLite 연결은 API가 받은 데이터를 메모리에만 두지 않고 실제 DB 파일에 저장하는 첫 단계입니다. 입문 단계에서는 별도 DB 서버가 필요 없는 SQLite로 시작하면 구조를 이해하기 쉽습니다.

FastAPI에서 데이터베이스를 연결하는 방법은 여러 가지입니다.
SQLAlchemy를 직접 써도 되고, 비동기 DB 라이브러리를 써도 됩니다.

다만 처음 배우는 단계라면 SQLModel을 사용하는 방식이 편합니다. SQLModel은 Pydantic과 SQLAlchemy를 기반으로 만들어진 라이브러리라서, FastAPI의 요청·응답 모델과 DB 테이블 모델을 비슷한 문법으로 다룰 수 있습니다.

여기서는 2026년 5월 기준 FastAPI 공식 문서 흐름에 맞춰 FastAPI + SQLite + SQLModel 조합으로 간단한 메모 API를 만들어봅니다.

중요한 변경점도 하나 있습니다. 예전 FastAPI 예제에서는 @app.on_event("startup")을 자주 사용했지만, 현재는 앱 시작과 종료 시 실행할 작업을 lifespan으로 처리하는 방식이 권장됩니다. 이 글도 새로 작성하는 코드에 맞춰 lifespan 기준으로 설명합니다.


 

FastAPI에서 SQLite를 연결한다는 뜻

FastAPI만 사용하면 보통 이런 식으로 데이터를 다룹니다.

items = []

 

리스트나 딕셔너리에 데이터를 넣는 방식입니다.

이 방식은 테스트할 때는 편하지만 문제가 있습니다.
서버를 껐다 켜면 데이터가 사라집니다.

SQLite를 연결하면 데이터가 .db 파일에 저장됩니다.

예를 들어 아래처럼 파일이 생깁니다.

app.db

 

이 파일 안에 테이블이 만들어지고, API로 등록한 데이터가 저장됩니다.

흐름으로 보면 아래와 같습니다.

API 요청
↓
FastAPI 라우터
↓
SQLModel Session
↓
SQLite DB 파일

 

여기서 Session은 FastAPI 코드와 데이터베이스 사이에서 실제 저장, 조회, 삭제 작업을 처리하는 통로라고 보면 됩니다.


 

SQLite를 처음 DB로 쓰기 좋은 이유

SQLite는 별도 서버 설치가 필요 없습니다.

MySQL이나 PostgreSQL은 DB 서버를 실행하고, 계정과 비밀번호를 만들고, 포트 설정도 해야 합니다.
반면 SQLite는 파일 하나로 동작합니다.

그래서 FastAPI 입문 단계에서는 SQLite가 적당합니다.

SQLite
- 별도 DB 서버 설치 필요 없음
- 로컬 실습에 적합
- 작은 프로젝트나 테스트용으로 편함
- 파일 기반 DB라 구조를 이해하기 쉬움

 

물론 모든 서비스에 SQLite가 맞는 것은 아닙니다.
동시에 많은 사용자가 쓰는 서비스, 복잡한 운영 환경, 대규모 트래픽이 필요한 서비스라면 PostgreSQL이나 MySQL을 고려하는 게 일반적입니다.

하지만 FastAPI에서 DB 연결 구조를 처음 익히는 목적이라면 SQLite로 충분합니다.


 

SQLModel을 사용하는 이유

FastAPI를 배우다 보면 이런 이름들을 자주 보게 됩니다.

Pydantic
SQLAlchemy
SQLModel

 

처음에는 헷갈릴 수 있습니다.

간단히 나누면 이렇습니다.

도구 역할
Pydantic API 요청/응답 데이터 검증
SQLAlchemy 파이썬에서 SQL DB를 다루는 ORM
SQLModel Pydantic + SQLAlchemy를 FastAPI에 맞게 쉽게 사용하도록 만든 도구

 

여기서 중요한 점은 SQLModel이 완전히 새로운 DB 기술은 아니라는 것입니다.
SQLModel은 SQLAlchemy와 Pydantic 위에서 동작합니다.

입문자 입장에서는 장점이 큽니다.

DB 테이블 모델과 API 데이터 모델을 비슷한 문법으로 작성할 수 있기 때문입니다.
처음부터 SQLAlchemy의 모든 개념을 깊게 들어가기보다, FastAPI에서 DB가 연결되는 흐름을 먼저 익히기에 좋습니다.


 

프로젝트 구조 만들기

먼저 프로젝트 폴더를 하나 만듭니다.

mkdir fastapi-sqlite-example
cd fastapi-sqlite-example

 

가상환경을 만듭니다.

python -m venv .venv

 

macOS 또는 Linux라면 아래 명령어로 활성화합니다.

source .venv/bin/activate

 

Windows PowerShell이라면 아래처럼 실행합니다.

.venv\Scripts\Activate.ps1

 

파일 구조는 단순하게 시작합니다.

fastapi-sqlite-example/
├─ main.py
└─ app.db   # 실행 후 자동 생성

 

처음부터 폴더를 여러 개로 나누면 오히려 흐름이 안 보일 수 있습니다.
DB 연결 구조를 익힌 뒤에 database.py, models.py, routers/로 나누는 편이 낫습니다.


 

필요한 패키지 설치하기

FastAPI, Uvicorn, SQLModel을 설치합니다.

pip install fastapi "uvicorn[standard]" sqlmodel

 

각 패키지의 역할은 아래와 같습니다.

패키지 역할
fastapi API 서버 프레임워크
uvicorn FastAPI 앱을 실행하는 ASGI 서버
sqlmodel SQLite 같은 SQL DB를 파이썬 모델로 다루는 도구

 

설치가 끝났다면 main.py 파일을 만듭니다.


 

SQLite 연결 코드 작성하기

먼저 가장 기본적인 DB 연결 코드를 작성합니다.

# main.py

from fastapi import FastAPI
from sqlmodel import SQLModel, Field, Session, create_engine

sqlite_file_name = "app.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

app = FastAPI()

 

여기서 핵심은 이 부분입니다.

sqlite_url = f"sqlite:///{sqlite_file_name}"

 

sqlite:///app.db는 현재 프로젝트 폴더에 app.db라는 SQLite 파일을 사용하겠다는 뜻입니다.

그리고 이 코드는 DB 연결 엔진을 만듭니다.

engine = create_engine(sqlite_url, echo=True)

 

echo=True를 넣으면 SQLModel이 내부적으로 실행하는 SQL 문이 터미널에 출력됩니다.

입문 단계에서는 켜두는 것이 좋습니다.
API를 호출했을 때 어떤 SQL이 실행되는지 확인할 수 있기 때문입니다.

실제 운영 환경에서는 로그가 너무 많아질 수 있으므로 상황에 따라 끕니다.

engine = create_engine(sqlite_url, echo=False)

 


 

데이터 모델 만들기

이제 DB에 저장할 모델을 만듭니다.

여기서는 간단한 메모 API를 예시로 사용합니다.

# main.py

from fastapi import FastAPI
from sqlmodel import SQLModel, Field, Session, create_engine


sqlite_file_name = "app.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)

app = FastAPI()


class Memo(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    content: str

 

이 클래스가 SQLite의 테이블이 됩니다.

class Memo(SQLModel, table=True):

 

table=True가 붙어 있기 때문에 SQLModel은 이 클래스를 DB 테이블로 인식합니다.

각 필드는 테이블의 컬럼이 됩니다.

id: int | None = Field(default=None, primary_key=True)
title: str
content: str

 

의미는 아래와 같습니다.

필드 설명
id 메모 고유 번호
title 메모 제목
content 메모 내용

 

id는 처음 생성할 때 직접 넣지 않아도 됩니다.
SQLite가 자동으로 값을 만들 수 있도록 default=None으로 둡니다.


 

앱 시작 시 테이블 생성하기

모델만 작성한다고 실제 DB 테이블이 생기지는 않습니다.

테이블을 만들려면 아래 코드가 필요합니다.

def create_db_and_tables():
    SQLModel.metadata.create_all(engine)

 

SQLModel.metadata.create_all(engine)은 지금까지 table=True로 정의한 모델을 기준으로 DB 테이블을 생성합니다.

이미 테이블이 있다면 매번 새로 덮어쓰는 것은 아닙니다.
없는 테이블을 만들어주는 역할에 가깝습니다.

이 작업은 앱이 시작될 때 한 번 실행되면 됩니다.


 

lifespan으로 시작 작업 등록하기

예전 FastAPI 예제에서는 아래와 같은 코드를 자주 볼 수 있습니다.

@app.on_event("startup")
def on_startup():
    create_db_and_tables()

 

하지만 새로 작성하는 코드라면 lifespan 방식으로 익혀두는 편이 좋습니다.
앱 시작 시 실행할 작업은 yield 위에 작성하고, 앱 종료 시 실행할 작업은 yield 아래에 작성합니다.

from contextlib import asynccontextmanager

@asynccontextmanager
async def lifespan(app: FastAPI):
    create_db_and_tables()
    yield

app = FastAPI(lifespan=lifespan)

 

여기서는 서버가 시작될 때 SQLite 테이블을 생성해야 하므로 create_db_and_tables()yield 위에 둡니다.

정리하면 이 단계까지의 코드는 아래와 같습니다.

# main.py

from contextlib import asynccontextmanager

from fastapi import FastAPI
from sqlmodel import SQLModel, Field, Session, create_engine


sqlite_file_name = "app.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


class Memo(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    content: str


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(app: FastAPI):
    create_db_and_tables()
    yield


app = FastAPI(lifespan=lifespan)

 

이 상태로 서버를 실행하면 app.db 파일이 생성됩니다.

uvicorn main:app --reload

 

다만 아직 API 엔드포인트가 없기 때문에 저장이나 조회는 할 수 없습니다.
이제 데이터를 등록하는 API를 추가해봅니다.


 

데이터 저장 API 만들기

메모를 저장하는 POST API를 추가합니다.

@app.post("/memos/")
def create_memo(memo: Memo):
    with Session(engine) as session:
        session.add(memo)
        session.commit()
        session.refresh(memo)
        return memo

 

여기서 가장 중요한 부분은 Session, commit(), refresh()입니다.

with Session(engine) as session:

 

Session(engine)은 DB와 대화하기 위한 작업 단위를 만듭니다.

session.add(memo)

 

session.add(memo)는 저장할 객체를 세션에 추가합니다.
아직 DB 파일에 최종 저장된 상태는 아닙니다.

session.commit()

 

중요한 부분은 session.commit()입니다.
이 코드를 실행해야 변경 내용이 실제 DB에 반영됩니다.

session.refresh(memo)

 

session.refresh(memo)는 DB에 저장된 최신 값을 다시 가져옵니다.
특히 id처럼 DB가 자동으로 만든 값을 응답에 포함하려면 이 과정이 필요합니다.

전체 코드는 아래처럼 됩니다.

# main.py

from contextlib import asynccontextmanager

from fastapi import FastAPI
from sqlmodel import SQLModel, Field, Session, create_engine


sqlite_file_name = "app.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


class Memo(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    content: str


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(app: FastAPI):
    create_db_and_tables()
    yield


app = FastAPI(lifespan=lifespan)


@app.post("/memos/")
def create_memo(memo: Memo):
    with Session(engine) as session:
        session.add(memo)
        session.commit()
        session.refresh(memo)
        return memo

 


 

데이터 조회 API 만들기

저장한 메모 목록을 조회하는 API도 추가합니다.

SQLModel에서는 select()를 사용해 데이터를 조회할 수 있습니다.

from sqlmodel import select

 

상단 import를 수정합니다.

from sqlmodel import SQLModel, Field, Session, create_engine, select

 

그리고 조회 API를 추가합니다.

@app.get("/memos/")
def read_memos():
    with Session(engine) as session:
        statement = select(Memo)
        memos = session.exec(statement).all()
        return memos

 

전체 코드는 아래와 같습니다.

# main.py

from contextlib import asynccontextmanager

from fastapi import FastAPI
from sqlmodel import SQLModel, Field, Session, create_engine, select


sqlite_file_name = "app.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"

engine = create_engine(sqlite_url, echo=True)


class Memo(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    content: str


def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(app: FastAPI):
    create_db_and_tables()
    yield


app = FastAPI(lifespan=lifespan)


@app.post("/memos/")
def create_memo(memo: Memo):
    with Session(engine) as session:
        session.add(memo)
        session.commit()
        session.refresh(memo)
        return memo


@app.get("/memos/")
def read_memos():
    with Session(engine) as session:
        statement = select(Memo)
        memos = session.exec(statement).all()
        return memos

 

이제 메모를 저장하고 다시 조회할 수 있습니다.


 

단일 메모 조회 API 추가하기

목록 조회만 있으면 조금 아쉽습니다.

이번에는 id로 하나의 메모만 조회하는 API를 추가합니다.

from fastapi import FastAPI, HTTPException

 

상단 import를 수정합니다.

from fastapi import FastAPI, HTTPException
from sqlmodel import SQLModel, Field, Session, create_engine, select

 

그리고 아래 코드를 추가합니다.

@app.get("/memos/{memo_id}")
def read_memo(memo_id: int):
    with Session(engine) as session:
        memo = session.get(Memo, memo_id)

        if memo is None:
            raise HTTPException(status_code=404, detail="Memo not found")

        return memo

 

session.get(Memo, memo_id)는 기본키 기준으로 데이터를 찾습니다.

데이터가 없으면 None이 반환됩니다.
이때 단순히 빈 값을 반환하기보다 404 Not Found를 보내는 편이 API답습니다.


 

삭제 API 추가하기

메모를 삭제하는 API도 만들어봅니다.

@app.delete("/memos/{memo_id}")
def delete_memo(memo_id: int):
    with Session(engine) as session:
        memo = session.get(Memo, memo_id)

        if memo is None:
            raise HTTPException(status_code=404, detail="Memo not found")

        session.delete(memo)
        session.commit()

        return {"message": "Memo deleted"}

 

삭제 흐름은 단순합니다.

  1. id로 메모를 찾습니다.
  2. 없으면 404를 반환합니다.
  3. 있으면 삭제합니다.
  4. session.commit()으로 DB에 반영합니다.

 

최종 코드

여기까지 합치면 최종 코드는 아래와 같습니다.

코드만 복사해서 실행해도 흐름을 이해할 수 있도록 주요 구간마다 주석을 넣었습니다.

# main.py

from contextlib import asynccontextmanager

from fastapi import FastAPI, HTTPException
from sqlmodel import SQLModel, Field, Session, create_engine, select


# 1. SQLite DB 연결 설정
sqlite_file_name = "app.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url, echo=True)


# 2. DB 테이블 모델 정의
class Memo(SQLModel, table=True):
    id: int | None = Field(default=None, primary_key=True)
    title: str
    content: str


# 3. 앱 시작 시 DB 테이블 생성
def create_db_and_tables():
    SQLModel.metadata.create_all(engine)


@asynccontextmanager
async def lifespan(app: FastAPI):
    create_db_and_tables()
    yield


app = FastAPI(lifespan=lifespan)


# 4. 메모 생성 API
@app.post("/memos/")
def create_memo(memo: Memo):
    with Session(engine) as session:
        session.add(memo)
        session.commit()
        session.refresh(memo)
        return memo


# 5. 메모 목록 조회 API
@app.get("/memos/")
def read_memos():
    with Session(engine) as session:
        statement = select(Memo)
        memos = session.exec(statement).all()
        return memos


# 6. 단일 메모 조회 API
@app.get("/memos/{memo_id}")
def read_memo(memo_id: int):
    with Session(engine) as session:
        memo = session.get(Memo, memo_id)

        if memo is None:
            raise HTTPException(status_code=404, detail="Memo not found")

        return memo


# 7. 메모 삭제 API
@app.delete("/memos/{memo_id}")
def delete_memo(memo_id: int):
    with Session(engine) as session:
        memo = session.get(Memo, memo_id)

        if memo is None:
            raise HTTPException(status_code=404, detail="Memo not found")

        session.delete(memo)
        session.commit()

        return {"message": "Memo deleted"}

 

이 코드만으로도 간단한 SQLite 기반 메모 API가 동작합니다.


 

서버 실행하기

터미널에서 실행합니다.

uvicorn main:app --reload

 

또는 FastAPI CLI를 사용한다면 아래처럼 실행할 수도 있습니다.

fastapi dev main.py

 

실행 후 브라우저에서 Swagger 문서에 접속합니다.

http://127.0.0.1:8000/docs

 

FastAPI는 자동으로 API 문서를 생성해줍니다.
여기서 POST /memos/, GET /memos/, GET /memos/{memo_id}, DELETE /memos/{memo_id}를 직접 테스트할 수 있습니다.


 

Swagger에서 메모 저장 테스트하기

POST /memos/를 열고 Try it out을 누릅니다.

요청 Body에 아래처럼 입력합니다.

{
  "title": "FastAPI SQLite 연습",
  "content": "SQLModel을 사용해서 SQLite에 데이터를 저장해본다."
}

 

응답은 대략 이런 형태로 나옵니다.

{
  "id": 1,
  "title": "FastAPI SQLite 연습",
  "content": "SQLModel을 사용해서 SQLite에 데이터를 저장해본다."
}

 

여기서 id는 직접 입력하지 않았지만 DB 저장 후 자동으로 생성됩니다.

그다음 GET /memos/를 실행하면 저장된 데이터가 목록으로 나옵니다.

[
  {
    "id": 1,
    "title": "FastAPI SQLite 연습",
    "content": "SQLModel을 사용해서 SQLite에 데이터를 저장해본다."
  }
]

 

서버를 껐다 켜도 app.db 파일이 남아 있다면 데이터도 유지됩니다.


 

자주 헷갈리는 부분

1. app.db 파일이 안 보일 때

서버를 실행하기 전에는 app.db 파일이 없을 수 있습니다.

이 파일은 아래 코드가 실행되면서 생성됩니다.

SQLModel.metadata.create_all(engine)

 

즉, 서버를 한 번 실행한 뒤 프로젝트 폴더를 확인해보면 됩니다.


2. session.commit()을 안 하면 저장되지 않는다

아래 코드만 쓰면 DB에 바로 저장되는 것처럼 보일 수 있습니다.

session.add(memo)

 

하지만 실제 반영은 session.commit()에서 일어납니다.

session.commit()

 

입문 단계에서 자주 하는 실수가 add()만 하고 commit()을 빼먹는 것입니다.


3. session.refresh()는 왜 필요할까?

session.refresh(memo)

 

이 코드는 DB에 저장된 최신 상태를 다시 가져옵니다.

특히 id처럼 DB가 자동으로 만든 값은 저장 전에는 알 수 없습니다.
session.refresh(memo)를 호출하면 방금 저장된 데이터의 id까지 포함해서 응답할 수 있습니다.


4. lifespan에서 yield는 왜 필요할까?

lifespan은 앱의 시작과 종료 흐름을 하나의 함수에서 다룹니다.

@asynccontextmanager
async def lifespan(app: FastAPI):
    create_db_and_tables()
    yield

 

yield 위쪽은 앱이 시작될 때 실행됩니다.
yield 아래쪽은 앱이 종료될 때 실행됩니다.

이 예제에서는 종료 시 따로 처리할 작업이 없기 때문에 yield 아래에는 아무 코드도 작성하지 않았습니다.


5. SQLite 파일을 지우면 데이터도 사라진다

SQLite는 파일 기반 DB입니다.

이 예제에서는 데이터가 app.db에 저장됩니다.

따라서 app.db 파일을 삭제하면 테이블과 데이터도 함께 사라집니다.
실습 중 DB 상태가 꼬였을 때는 파일을 지우고 다시 실행하는 방식으로 초기화할 수 있습니다.

단, 실제 중요한 데이터가 들어 있는 DB 파일은 함부로 삭제하면 안 됩니다.


 

SQLite를 계속 써도 되는 경우

SQLite는 단순 실습용으로만 쓰는 도구는 아닙니다.
작은 규모의 앱이나 로컬 도구에서는 충분히 유용합니다.

아래 상황이라면 SQLite로 시작해도 괜찮습니다.

- FastAPI DB 연결을 처음 배우는 경우
- 개인 프로젝트를 만드는 경우
- 로컬에서만 사용하는 자동화 도구를 만드는 경우
- 관리자 한두 명만 쓰는 작은 내부 도구를 만드는 경우
- 테스트용 API 서버를 빠르게 만들고 싶은 경우

 

반대로 아래 상황이라면 PostgreSQL이나 MySQL 같은 DB를 검토하는 편이 좋습니다.

- 여러 사용자가 동시에 많이 접속하는 서비스
- 운영 서버에서 안정적인 DB 관리가 필요한 경우
- 사용자 계정, 결제, 권한 같은 중요한 데이터를 다루는 경우
- 배포 환경에서 DB 백업과 마이그레이션이 중요한 경우

 

여기서 중요한 점은 SQLite가 나쁘다는 뜻이 아닙니다.
역할이 다를 뿐입니다.

FastAPI 입문 단계에서는 SQLite가 구조를 이해하기 좋고, 운영 서비스로 갈수록 더 강한 DB 관리 기능이 필요해집니다.


 

마무리

FastAPI에서 SQLite를 연결할 때 핵심은 세 가지입니다.

첫째, create_engine()으로 SQLite DB 파일과 연결합니다.
둘째, SQLModel 클래스로 테이블 구조를 만듭니다.
셋째, Session을 통해 데이터를 저장하고 조회합니다.

그리고 새로 작성하는 FastAPI 코드라면 앱 시작 작업은 @app.on_event("startup")보다 lifespan 방식으로 익혀두는 편이 좋습니다.

처음부터 PostgreSQL, Alembic, 비동기 DB 세션까지 한 번에 넣으면 구조가 흐려질 수 있습니다.
먼저 SQLite로 저장과 조회 흐름을 손에 익히고, 그다음 실제 서비스 구조로 넘어가는 편이 더 안정적입니다.