{"slug": "fastapi-for-ai-engineers-part-3-connecting-to-a-database", "title": "FastAPI for AI Engineers - Part 3: Connecting to a database", "summary": "A developer has connected a FastAPI application to an SQLite database using SQLAlchemy, replacing the previous in-memory Python list storage that caused data loss on server restart. The implementation uses SQLAlchemy's Object Relational Mapper (ORM) to interact with database tables through Python classes, with the database configuration handled in a dedicated `database.py` file. This setup ensures student data persists across application restarts, addressing a critical limitation of the earlier CRUD API.", "body_md": "In the previous article, we explored how to build our first CRUD API using FastAPI. While our API worked correctly, there was one major problem.\n\nWe were storing data inside Python lists, which exist only in memory.\n\nIf you've ever wondered how applications like Instagram, LinkedIn, or ChatGPT remember information even after a server restart, the answer is simple: databases.\n\nIn this article, we'll solve the problem of in-memory storage by connecting our FastAPI application to SQLite using SQLAlchemy.\n\nIf you haven't read the previous post, check it out:\n\nBy the end of this article, you'll understand:\n\nPreviously, our application stored students inside a Python list.\n\n```\nstudents = [\n    {\n        \"id\": 1,\n        \"name\": \"Ananya\",\n        \"department\": \"CSE\",\n        \"cgpa\": 8.9\n    }\n]\n```\n\nThis worked for learning CRUD operations.\n\nHowever, consider what happens when the server restarts:\n\n```\nFastAPI Server Stops\n        ↓\nPython Memory Cleared\n        ↓\nAll Student Data Lost\n```\n\nThis is unacceptable in real-world applications.\n\nWe need a place where data can survive application restarts.\n\nThis is where databases come in.\n\nSQLite is a lightweight relational database.\n\nUnlike MySQL or PostgreSQL, SQLite doesn't require a separate database server.\n\nInstead, everything is stored inside a single file.\n\n```\nstudents.db\n```\n\nAdvantages of SQLite:\n\nFor this article, we'll use SQLite.\n\nBefore SQLAlchemy, developers often wrote raw SQL queries.\n\nExample:\n\n```\nSELECT * FROM students;\n```\n\nWhile SQL is powerful, writing queries everywhere quickly becomes difficult to maintain.\n\nSQLAlchemy solves this problem using an ORM.\n\nORM stands for Object Relational Mapper.\n\nIt allows us to interact with database tables using Python classes.\n\nThink of it like a translator.\n\n| Database | Python |\n|---|---|\n| Table | Class |\n| Row | Object |\n| Column | Attribute |\n\nFor example:\n\nDatabase table:\n\n```\nstudents\n\nid     name     department     cgpa\n1      Ananya   CSE            8.9\n```\n\nbecomes:\n\n```\nclass Student(Base):\n    ...\n```\n\nInstead of writing SQL manually, we work with Python objects.\n\nSQLAlchemy generates SQL behind the scenes.\n\nCreate the following structure:\n\n```\nproject/\n│\n├── database.py\n├── models.py\n├── schemas.py\n├── main.py\n└── students.db\n```\n\nEach file has a specific responsibility.\n\nResponsible for:\n\nResponsible for:\n\nResponsible for:\n\nResponsible for:\n\n```\npip install sqlalchemy\n```\n\nIf you haven't installed FastAPI yet:\n\n```\npip install fastapi uvicorn\n```\n\nCreate a file named `database.py`\n\n``` python\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.orm import declarative_base\nfrom sqlalchemy.orm import sessionmaker\n\nDATABASE_URL = \"sqlite:///./students.db\"\n\nengine = create_engine(\n    DATABASE_URL,\n    connect_args={\"check_same_thread\": False} #allows the same connection to be used across threads\n)\n\nSessionLocal = sessionmaker(\n    autocommit=False, \n    autoflush=False,\n    bind=engine\n)\n\nBase = declarative_base()\n```\n\nNormally, SQLAlchemy uses transactional mode:\n\nYou make changes → they are staged in the session → you call commit() to persist them.\n\nIf autocommit is enabled, each statement is committed immediately (like SQLite’s default).\n\nWhen autoflush=True (default), SQLAlchemy automatically flushes pending changes to the database before executing a query.\n\nFlush means:\n\nSynchronize in-memory changes with the database inside the current transaction.\n\nDoes not commit — changes are still rollback-able until you call commit().\n\n```\nengine = create_engine(...)\n```\n\nSQLAlchemy needs a way to communicate with the database.\n\nThe Engine object acts as the bridge between FastAPI and SQLite.\n\nWhenever we:\n\nSQLAlchemy uses the engine to talk to the database.\n\n```\nSessionLocal = sessionmaker(...)\n```\n\nA session represents a conversation with the database.\n\nImagine visiting a bank:\n\nA database session works similarly.\n\nEvery database operation happens through a session.\n\n```\nBase = declarative_base()\n```\n\nEvery database model we create will inherit from Base.\n\nSQLAlchemy uses Base to keep track of all models and create tables automatically.\n\nAdd this function below the previous code.\n\n``` python\ndef get_db():\n\n    db = SessionLocal()\n\n    try:\n        yield db\n\n    finally:\n        db.close()\n```\n\nWithout this function, every route would need to create and close sessions manually.\n\nExample:\n\n``` python\n@app.get(\"/students\")\ndef get_students():\n\n    db = SessionLocal()\n\n    # Database operations\n\n    db.close()\n```\n\nThis becomes repetitive.\n\nInstead, FastAPI can automatically create and close sessions for us.\n\nLater we'll use:\n\n```\ndb: Session = Depends(get_db)\n```\n\nFastAPI will:\n\nThis is called Dependency Injection.\n\nCreate a file named `models.py`\n\n``` python\nfrom sqlalchemy import Column, Integer, String, Float\n\nfrom database import Base\n\nclass Student(Base):\n\n    __tablename__ = \"students\"\n\n    id = Column(Integer, primary_key=True, index=True)\n    name = Column(String)\n    department = Column(String)\n    cgpa = Column(Float)\n__tablename__ = \"students\"\n```\n\nThis creates a table named:\n\n```\nstudents\nid = Column(Integer, primary_key=True)\n```\n\nCreates the primary key.\n\nEvery student must have a unique ID.\n\n```\nname = Column(String)\n```\n\nCreates a text column.\n\nThe same applies to department.\n\n```\ncgpa = Column(Float)\n```\n\nCreates a floating-point column.\n\nCreate a file named `schemas.py`\n\n``` python\nfrom pydantic import BaseModel\n\nclass StudentCreate(BaseModel):\n    name: str\n    department: str\n    cgpa: float\n\nclass StudentResponse(StudentCreate):\n    id: int\n\n    class Config:\n        from_attributes = True\n```\n\nSchemas define what data our API expects.\n\nFor now, think of schemas as blueprints.\n\nWe're using Pydantic behind the scenes.\n\nWe'll explore:\n\nin a dedicated article later in this series.\n\n``` python\nfrom fastapi import FastAPI, Depends\nfrom sqlalchemy.orm import Session\n\nimport models\nimport schemas\n\nfrom database import engine, get_db\n\napp = FastAPI()\n\nmodels.Base.metadata.create_all(bind=engine)\nmodels.Base.metadata.create_all(bind=engine)\n```\n\nWhen FastAPI starts:\n\nOur Student table is now created inside SQLite.\n\n```\n@app.post(\"/student\", response_model=schemas.StudentResponse)\ndef create_student(\n    student: schemas.StudentCreate,\n    db: Session = Depends(get_db)\n):\n\n    new_student = models.Student(\n        name=student.name,\n        department=student.department,\n        cgpa=student.cgpa\n    )\n\n    db.add(new_student)\n\n    db.commit()\n\n    db.refresh(new_student)\n\n    return new_student\ndb.add(new_student)\n```\n\nAdds the object to the session.\n\n```\ndb.commit()\n```\n\nPermanently saves data to the database.\n\n```\ndb.refresh(new_student)\n```\n\nReloads the object from the database.\n\nThis is useful because the database automatically generates the ID.\n\nGet all students.\n\n``` python\n@app.get(\"/students\")\ndef get_students(\n    db: Session = Depends(get_db)\n):\n\n    return db.query(models.Student).all()\n```\n\nGet a student by ID.\n\n``` python\n@app.get(\"/student/{id}\")\ndef get_student(\n    id: int,\n    db: Session = Depends(get_db)\n):\n\n    return (\n        db.query(models.Student)\n        .filter(models.Student.id == id)\n        .first()\n    )\npython\n@app.put(\"/student/{id}\")\ndef update_student(\n    id: int,\n    updated_student: schemas.StudentCreate,\n    db: Session = Depends(get_db)\n):\n\n    student = (\n        db.query(models.Student)\n        .filter(models.Student.id == id)\n        .first()\n    )\n\n    if not student:\n        return {\"message\": \"Student not found\"}\n\n    student.name = updated_student.name\n    student.department = updated_student.department\n    student.cgpa = updated_student.cgpa\n\n    db.commit()\n\n    db.refresh(student)\n\n    return student\npython\n@app.delete(\"/student/{id}\")\ndef delete_student(\n    id: int,\n    db: Session = Depends(get_db)\n):\n\n    student = (\n        db.query(models.Student)\n        .filter(models.Student.id == id)\n        .first()\n    )\n\n    if not student:\n        return {\"message\": \"Student not found\"}\n\n    db.delete(student)\n\n    db.commit()\n\n    return {\"message\": \"Student deleted successfully\"}\n```\n\nStart the server:\n\n```\nuvicorn main:app --reload\n```\n\nOpen:\n\n```\nhttp://127.0.0.1:8000/docs\n```\n\nUse Swagger UI to:\n\nThe good news is that SQLAlchemy makes switching databases extremely easy.\n\nCurrent SQLite connection:\n\n```\nDATABASE_URL = \"sqlite:///./students.db\"\n```\n\nMySQL connection:\n\n```\nMYSQL_USER = \"root\"\nDB_PASSWORD = \"123456\" # use your MySQL login password\nMYSQL_HOST = 'localhost'\nMYSQL_PORT = '3306'\nMYSQL_DATABASE = 'fastapi_db'\n\nDATABASE_URL = f\"mysql+pymysql://{MYSQL_USER}:{DB_PASSWORD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DATABASE}\"\n```\n\nInstall the MySQL driver:\n\n```\npip install pymysql\n```\n\nEverything else remains almost identical. Ensure you have MySQL in your desktop, open MySQL WorkBench and connect to database to see the database and tables in it.\n\nThis is one of the biggest advantages of using an ORM.\n\n```\nClient Request\n      │\n      ▼\nFastAPI Route\n      │\n      ▼\nPydantic Schema\n      │\n      ▼\nDatabase Session\n      │\n      ▼\nSQLAlchemy Model\n      │\n      ▼\nSQLite / MySQL\n```\n\nWhen a user creates a student:\n\nWe've now moved beyond in-memory storage and built our first database-backed FastAPI application.\n\nMost production AI applications use the same architecture, whether they're storing chat histories, user profiles, agent memory, evaluation results, or feedback data.\n\nIn the next article, we'll take a deeper look at Pydantic and understand how FastAPI validates incoming data automatically.", "url": "https://wpnews.pro/news/fastapi-for-ai-engineers-part-3-connecting-to-a-database", "canonical_source": "https://dev.to/zeroshotanu/fastapi-for-ai-engineers-part-3-connecting-to-a-database-30ca", "published_at": "2026-06-06 06:27:51+00:00", "updated_at": "2026-06-06 06:41:39.406734+00:00", "lang": "en", "topics": ["mlops"], "entities": ["FastAPI", "SQLite", "SQLAlchemy", "Instagram", "LinkedIn", "ChatGPT"], "alternates": {"html": "https://wpnews.pro/news/fastapi-for-ai-engineers-part-3-connecting-to-a-database", "markdown": "https://wpnews.pro/news/fastapi-for-ai-engineers-part-3-connecting-to-a-database.md", "text": "https://wpnews.pro/news/fastapi-for-ai-engineers-part-3-connecting-to-a-database.txt", "jsonld": "https://wpnews.pro/news/fastapi-for-ai-engineers-part-3-connecting-to-a-database.jsonld"}}