Skip to content

Commit

Permalink
Add FastAPI quickstart (#8347)
Browse files Browse the repository at this point in the history
  • Loading branch information
beerose authored and 1st1 committed Feb 21, 2025
1 parent 5e65220 commit d734944
Show file tree
Hide file tree
Showing 15 changed files with 903 additions and 27 deletions.
78 changes: 78 additions & 0 deletions docs/intro/quickstart/connecting/fastapi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
.. _ref_quickstart_fastapi_connecting:

==========================
Connecting to the database
==========================

.. edb:split-section::
Before diving into the application, let's take a quick look at how to connect to the database from your code. We will intialize a client and use it to make a simple, static query to the database, and log the result to the console.

.. note::

Notice that the ``create_async_client`` function isn't being passed any connection details. With |Gel|, you do not need to come up with your own scheme for how to build the correct database connection credentials and worry about leaking them into your code. You simply use |Gel| "projects" for local development, and set the appropriate environment variables in your deployment environments, and the ``create_async_client`` function knows what to do!

.. edb:split-point::
.. code-block:: python
:caption: ./test.py
import gel
import asyncio
async def main():
client = gel.create_async_client()
result = await client.query_single("select 'Hello from Gel!';")
print(result)
asyncio.run(main())
.. code-block:: sh
$ python test.py
Hello from Gel!
.. edb:split-section::
In Python, we write EdgeQL queries directly as strings. This gives us the full power and expressiveness of EdgeQL while maintaining type safety through Gel's strict schema. Let's try inserting a few ``Deck`` objects into the database and then selecting them back.

.. edb:split-point::
.. code-block:: python-diff
:caption: ./test.py
import gel
import asyncio
async def main():
client = gel.create_async_client()
- result = await client.query_single("select 'Hello from Gel!';")
- print(result)
+ await client.query("""
+ insert Deck { name := "I am one" }
+ """)
+
+ await client.query("""
+ insert Deck { name := "I am two" }
+ """)
+
+ decks = await client.query("""
+ select Deck {
+ id,
+ name
+ }
+ """)
+
+ for deck in decks:
+ print(f"ID: {deck.id}, Name: {deck.name}")
+
+ await client.query("delete Deck")
asyncio.run(main())
.. code-block:: sh
$ python test.py
Hello from Gel!
ID: f4cd3e6c-ea75-11ef-83ec-037350ea8a6e, Name: I am one
ID: f4cf27ae-ea75-11ef-83ec-3f7b2fceab24, Name: I am two
7 changes: 4 additions & 3 deletions docs/intro/quickstart/connecting/index.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
.. edb:env-switcher::
==========
Connecting
==========
==========================
Connecting to the database
==========================

.. toctree::
:maxdepth: 3
:hidden:

nextjs
fastapi
90 changes: 90 additions & 0 deletions docs/intro/quickstart/inheritance/fastapi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.. _ref_quickstart_fastapi_inheritance:

========================
Adding shared properties
========================

.. edb:split-section::
One common pattern in applications is to add shared properties to the schema that are used by multiple objects. For example, you might want to add a ``created_at`` and ``updated_at`` property to every object in your schema. You can do this by adding an abstract type and using it as a mixin for your other object types.

.. code-block:: sdl-diff
:caption: dbschema/default.gel
module default {
+ abstract type Timestamped {
+ required created_at: datetime {
+ default := datetime_of_statement();
+ };
+ required updated_at: datetime {
+ default := datetime_of_statement();
+ };
+ }
+
- type Deck {
+ type Deck extending Timestamped {
required name: str;
description: str;
cards := (
select .<deck[is Card]
order by .order
);
};
- type Card {
+ type Card extending Timestamped {
required order: int64;
required front: str;
required back: str;
required deck: Deck;
}
}
.. edb:split-section::
Since you don't have historical data for when these objects were actually created or modified, the migration will fall back to the default values set in the ``Timestamped`` type.

.. code-block:: sh
$ gel migration create
did you create object type 'default::Timestamped'? [y,n,l,c,b,s,q,?]
> y
did you alter object type 'default::Card'? [y,n,l,c,b,s,q,?]
> y
did you alter object type 'default::Deck'? [y,n,l,c,b,s,q,?]
> y
Created /home/strinh/projects/flashcards/dbschema/migrations/00004-m1d2m5n.edgeql, id: m1d2m5n5ajkalyijrxdliioyginonqbtfzihvwdfdmfwodunszstya
$ gel migrate
Applying m1d2m5n5ajkalyijrxdliioyginonqbtfzihvwdfdmfwodunszstya (00004-m1d2m5n.edgeql)
... parsed
... applied
.. edb:split-section::
Update the ``get_decks`` query to sort the decks by ``updated_at`` in descending order.

.. code-block:: python-diff
:caption: main.py
@app.get("/decks", response_model=List[Deck])
async def get_decks():
decks = await client.query("""
select Deck {
id,
name,
description,
cards := (
select .cards {
id,
front,
back
}
order by .order
)
}
+ order by .updated_at desc
""")
return decks
7 changes: 4 additions & 3 deletions docs/intro/quickstart/inheritance/index.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
.. edb:env-switcher::
===========
Inheritance
===========
========================
Adding shared properties
========================

.. toctree::
:maxdepth: 3
:hidden:

nextjs
fastapi

94 changes: 94 additions & 0 deletions docs/intro/quickstart/modeling/fastapi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
.. _ref_quickstart_fastapi_modeling:

=================
Modeling the data
=================

.. edb:split-section::
The flashcards application has a simple data model, but it's interesting enough to utilize many unique features of the |Gel| schema language.

Looking at the mock data in the example JSON file ``./deck-edgeql.json``, you can see this structure in the JSON. There is a ``Card`` class that describes a single flashcard, which contains two required string properties: ``front`` and ``back``. Each ``Deck`` object has zero or more ``Card`` objects in a list.

.. code-block:: python
from pydantic import BaseModel
class CardBase(BaseModel):
front: str
back: str
class Card(CardBase):
id: str
class DeckBase(BaseModel):
name: str
description: Optional[str] = None
class Deck(DeckBase):
id: str
cards: List[Card]
.. edb:split-section::
Starting with this simple model, add these types to the :dotgel:`dbschema/default` schema file. As you can see, the types closely mirror the JSON mock data.

Also of note, the link between ``Card`` and ``Deck`` objects creates a "1-to-n" relationship, where each ``Deck`` object has a link to zero or more ``Card`` objects. When you query the ``Deck.cards`` link, the cards will be unordered, so the ``Card`` type needs an explicit ``order`` property to allow sorting them at query time.

By default, when you try to delete an object that is linked to another object, the database will prevent you from doing so. We want to support removing a ``Card``, so we define a deletion policy on the ``cards`` link that allows deleting the target of this link.

.. code-block:: sdl-diff
:caption: dbschema/default.gel
module default {
+ type Card {
+ required order: int64;
+ required front: str;
+ required back: str;
+ };
+
+ type Deck {
+ required name: str;
+ description: str;
+ multi cards: Card {
+ constraint exclusive;
+ on target delete allow;
+ };
+ };
};
.. edb:split-section::
Congratulations! This first version of the data model's schema is *stored in a file on disk*. Now you need to signal the database to actually create types for ``Deck`` and ``Card`` in the database.

To make |Gel| do that, you need to do two quick steps:

1. **Create a migration**: a "migration" is a file containing a set of low level instructions that define how the database schema should change. It records any additions, modifications, or deletions to your schema in a way that the database can understand.

.. note::

When you are changing existing schema, the CLI migration tool might ask questions to ensure that it understands your changes exactly. Since the existing schema was empty, the CLI will skip asking any questions and simply create the migration file.

2. **Apply the migration**: This executes the migration file on the database, instructing |Gel| to implement the recorded changes in the database. Essentially, this step updates the database structure to match your defined schema, ensuring that the ``Deck`` and ``Card`` types are created and ready for use.

.. code-block:: sh
$ uvx gel migration create
Created ./dbschema/migrations/00001-m125ajr.edgeql, id: m125ajrbqp7ov36s7aniefxc376ofxdlketzspy4yddd3hrh4lxmla
$ uvx gel migrate
Applying m125ajrbqp7ov36s7aniefxc376ofxdlketzspy4yddd3hrh4lxmla (00001-m125ajr.edgeql)
... parsed
... applied
.. edb:split-section::
Take a look at the schema you've generated in the built-in database UI. Use this tool to visualize your data model and see the object types and links you've defined.

.. edb:split-point::
.. code-block:: sh
$ uvx gel ui
.. image:: images/schema-ui.png
9 changes: 4 additions & 5 deletions docs/intro/quickstart/modeling/index.rst
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
.. edb:env-switcher::
========
Modeling
========
=================
Modeling the data
=================

.. toctree::
:maxdepth: 3
:hidden:

nextjs


fastapi
2 changes: 1 addition & 1 deletion docs/intro/quickstart/modeling/nextjs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Modeling the data
The flashcards application has a simple data model, but it's interesting enough to utilize many unique features of the |Gel| schema language.

Looking at the mock data in our example JSON file ``./deck-edgeql.json``, you can see this structure in the JSON. There is a ``Card`` type that describes a single flashcard, which contains two required string properties: ``front`` and ``back``. Each ``Deck`` object has a link to zero or more ``Card`` objects in an array.
Looking at the mock data in the example JSON file ``./deck-edgeql.json``, you can see this structure in the JSON. There is a ``Card`` type that describes a single flashcard, which contains two required string properties: ``front`` and ``back``. Each ``Deck`` object has zero or more ``Card`` objects in an array.

.. code-block:: typescript
Expand Down
58 changes: 58 additions & 0 deletions docs/intro/quickstart/overview/fastapi.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
.. _ref_quickstart_fastapi:

==========
Quickstart
==========

Welcome to the quickstart tutorial! In this tutorial, you will update a FastAPI
backend for a Flashcards application to use |Gel| as your data layer. The
application will let users build and manage their own study decks, with each
flashcard featuring customizable text on both sides - making it perfect for
studying, memorization practice, or creating educational games.

Don't worry if you're new to |Gel| - you will be up and running with a working
FastAPI backend and a local |Gel| database in just about **5 minutes**. From
there, you will replace the static mock data with a |Gel| powered data layer in
roughly 30-45 minutes.

By the end of this tutorial, you will be comfortable with:

* Creating and updating a database schema
* Running migrations to evolve your data
* Writing EdgeQL queries
* Building an app backed by |Gel|


Features of the flashcards app
------------------------------

* Create, edit, and delete decks
* Add/remove cards with front/back content
* Clean, type-safe schema with |Gel|

Requirements
------------

Before you start, you need:

* Basic familiarity with Python and FastAPI
* Python 3.8+ on a Unix-like OS (Linux, macOS, or WSL)
* A code editor you love

Why |Gel| for FastAPI?
----------------------

* **Type Safety**: Catch data errors before runtime
* **Rich Modeling**: Use object types and links to model relations
* **Modern Tooling**: Python-friendly schemas and migrations
* **Performance**: Efficient queries for complex data
* **Developer Experience**: An intuitive query language (EdgeQL)

Need Help?
----------

If you run into issues while following this tutorial:

- Check the `|Gel| documentation <https://docs.geldata.com>`_
- Visit our `community Discord <https://discord.gg/gel>`_
- File an issue on `GitHub <https://github.com/geldata/gel>`_
Loading

0 comments on commit d734944

Please sign in to comment.