DEV Community

Cover image for ToDo App in FastAPI with Jinja2 Template
Arpit
Arpit

Posted on • Originally published at codesnail.com

ToDo App in FastAPI with Jinja2 Template

Create Virtual Environment

virtualenv env
Enter fullscreen mode Exit fullscreen mode

and activate it

source env/bin/activate
Enter fullscreen mode Exit fullscreen mode

Installation

pip install fastapi "uvicorn[standard]" jinja2 python-multipart sqlalchemy
Enter fullscreen mode Exit fullscreen mode

Now make main.py, database.py, and model.py in the same directory (in todoapp directory) and also make the templates and static directory, and its look like this,

structure

main.py

# main.py

from fastapi import FastAPI, Request, Depends, Form, status
from fastapi.templating import Jinja2Templates
import models
from database import engine, sessionlocal
from sqlalchemy.orm import Session
from fastapi.responses import RedirectResponse
from fastapi.staticfiles import StaticFiles

models.Base.metadata.create_all(bind=engine)

templates = Jinja2Templates(directory="templates")

app = FastAPI()

app.mount("/static", StaticFiles(directory="static"), name="static")

def get_db():
    db = sessionlocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/")
async def home(request: Request, db: Session = Depends(get_db)):
    todos = db.query(models.Todo).order_by(models.Todo.id.desc())
    return templates.TemplateResponse("index.html", {"request": request, "todos": todos})

@app.post("/add")
async def add(request: Request, task: str = Form(...), db: Session = Depends(get_db)):
    todo = models.Todo(task=task)
    db.add(todo)
    db.commit()
    return RedirectResponse(url=app.url_path_for("home"), status_code=status.HTTP_303_SEE_OTHER)

@app.get("/edit/{todo_id}")
async def add(request: Request, todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    return templates.TemplateResponse("edit.html", {"request": request, "todo": todo})

@app.post("/edit/{todo_id}")
async def add(request: Request, todo_id: int, task: str = Form(...), completed: bool = Form(False), db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    todo.task = task
    todo.completed = completed
    db.commit()
    return RedirectResponse(url=app.url_path_for("home"), status_code=status.HTTP_303_SEE_OTHER)

@app.get("/delete/{todo_id}")
async def add(request: Request, todo_id: int, db: Session = Depends(get_db)):
    todo = db.query(models.Todo).filter(models.Todo.id == todo_id).first()
    db.delete(todo)
    db.commit()
    return RedirectResponse(url=app.url_path_for("home"), status_code=status.HTTP_303_SEE_OTHER)
Enter fullscreen mode Exit fullscreen mode

database.py

# database.py

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

DB_URL = 'sqlite:///todo.sqlite3'

engine = create_engine(DB_URL, connect_args={'check_same_thread': False})
sessionlocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Enter fullscreen mode Exit fullscreen mode

models.py

# models.py

from sqlalchemy import Column, Integer, Boolean, Text
from database import Base

class Todo(Base):
    __tablename__ = 'todos'
    id = Column(Integer, primary_key=True)
    task = Column(Text)
    completed = Column(Boolean, default=False)

    def __repr__(self):
        return '<Todo %r>' % (self.id)
Enter fullscreen mode Exit fullscreen mode

Now letโ€™s make templates inside the templates directory

templates/base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ToDo App</title>
    <link rel="stylesheet" href="{{ url_for('static', path='css/main.css') }}">
</head>
<body>
    <main>
        <h1>ToDo App</h1>
        <br>
        {% block content %}

        {% endblock content %}
    </main>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

templates/index.html

{% extends 'base.html' %}

{% block content %}

    <form action="/add" method="post">
        <textarea name="task" rows="5" placeholder="Enter your task"></textarea>
        <button type="submit">Add</button>
    </form>

    <br>
    <h2>Tasks</h2>

    <div>
        {% for todo in todos %}
            <div class="task">
                {% if todo.completed %}
                    <strike>{{ todo.task }}</strike>
                {% else %}
                    {{ todo.task }}
                {% endif %}
                <small>
                    <a href="edit/{{ todo.id }}">Edit</a>
                    <a href="delete/{{ todo.id }}">Delete</a>
                </small>
            </div>
        {% endfor %}
    </div>

{% endblock content %}
Enter fullscreen mode Exit fullscreen mode

templates/edit.html

{% extends 'base.html' %}

{% block content %}
    <form action="/edit/{{todo.id}}" method="post">
        <textarea name="task" rows="5">{{todo.task}}</textarea>
        <label for="completed">Done?</label>
        <input type="checkbox" name="completed" {% if todo.completed %}checked{% endif %}>

        <button type="submit">Save</button>
    </form>
{% endblock content %}
Enter fullscreen mode Exit fullscreen mode

static/css/main.css

*{
    padding: 0;
    margin: 0;
    box-sizing: border-box;
}

body{
    font-family: 'Roboto', sans-serif;
    background: #f5f5f5;
}

main{
    width: 100%;
    max-width: 520px;
    margin: 0 auto;
    padding: 0 20px;
}

textarea{
    width: 100%;
    height: 100px;
    border: 1px solid #ccc;
    border-radius: 5px;
    padding: 10px;
    resize: none;
}

button{
    width: 100%;
    height: 40px;
    border: 1px solid #ccc;
    border-radius: 5px;
    background-color: #eee;
    color: #333;
    font-size: 16px;
    cursor: pointer;
}

.task{
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 8px 0;
    border-bottom: 1px solid #ccc;
}

.task:first-child{
    border-top: 1px solid #ccc;
}
Enter fullscreen mode Exit fullscreen mode

output

To run the FastAPI app type the following command and hit enter

uvicorn main:app --reload
Enter fullscreen mode Exit fullscreen mode

open link on browser (http://127.0.0.1:8000)

index page
index page

edit page
Image description

Source code on GitHub: https://github.com/SoniArpit/simple-todoapp-fastapi

Join developer community to share feelings, thoughts, random stuff -> https://awwsome.dev/ (made in django)

Bye <3

Top comments (0)