Skip to content

Commit 38f3015

Browse files
beerose1st1
authored andcommitted
Add FastAPI quickstart (#8347)
1 parent 6e3e63c commit 38f3015

File tree

15 files changed

+903
-27
lines changed

15 files changed

+903
-27
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
.. _ref_quickstart_fastapi_connecting:
2+
3+
==========================
4+
Connecting to the database
5+
==========================
6+
7+
.. edb:split-section::
8+
9+
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.
10+
11+
.. note::
12+
13+
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!
14+
15+
.. edb:split-point::
16+
17+
.. code-block:: python
18+
:caption: ./test.py
19+
20+
import gel
21+
import asyncio
22+
23+
async def main():
24+
client = gel.create_async_client()
25+
result = await client.query_single("select 'Hello from Gel!';")
26+
print(result)
27+
28+
asyncio.run(main())
29+
30+
.. code-block:: sh
31+
32+
$ python test.py
33+
Hello from Gel!
34+
35+
.. edb:split-section::
36+
37+
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.
38+
39+
.. edb:split-point::
40+
41+
.. code-block:: python-diff
42+
:caption: ./test.py
43+
44+
import gel
45+
import asyncio
46+
47+
async def main():
48+
client = gel.create_async_client()
49+
- result = await client.query_single("select 'Hello from Gel!';")
50+
- print(result)
51+
+ await client.query("""
52+
+ insert Deck { name := "I am one" }
53+
+ """)
54+
+
55+
+ await client.query("""
56+
+ insert Deck { name := "I am two" }
57+
+ """)
58+
+
59+
+ decks = await client.query("""
60+
+ select Deck {
61+
+ id,
62+
+ name
63+
+ }
64+
+ """)
65+
+
66+
+ for deck in decks:
67+
+ print(f"ID: {deck.id}, Name: {deck.name}")
68+
+
69+
+ await client.query("delete Deck")
70+
71+
asyncio.run(main())
72+
73+
.. code-block:: sh
74+
75+
$ python test.py
76+
Hello from Gel!
77+
ID: f4cd3e6c-ea75-11ef-83ec-037350ea8a6e, Name: I am one
78+
ID: f4cf27ae-ea75-11ef-83ec-3f7b2fceab24, Name: I am two
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
.. edb:env-switcher::
22
3-
==========
4-
Connecting
5-
==========
3+
==========================
4+
Connecting to the database
5+
==========================
66

77
.. toctree::
88
:maxdepth: 3
99
:hidden:
1010

1111
nextjs
12+
fastapi
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
.. _ref_quickstart_fastapi_inheritance:
2+
3+
========================
4+
Adding shared properties
5+
========================
6+
7+
.. edb:split-section::
8+
9+
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.
10+
11+
.. code-block:: sdl-diff
12+
:caption: dbschema/default.gel
13+
14+
module default {
15+
+ abstract type Timestamped {
16+
+ required created_at: datetime {
17+
+ default := datetime_of_statement();
18+
+ };
19+
+ required updated_at: datetime {
20+
+ default := datetime_of_statement();
21+
+ };
22+
+ }
23+
+
24+
- type Deck {
25+
+ type Deck extending Timestamped {
26+
required name: str;
27+
description: str;
28+
29+
cards := (
30+
select .<deck[is Card]
31+
order by .order
32+
);
33+
};
34+
35+
- type Card {
36+
+ type Card extending Timestamped {
37+
required order: int64;
38+
required front: str;
39+
required back: str;
40+
41+
required deck: Deck;
42+
}
43+
}
44+
45+
.. edb:split-section::
46+
47+
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.
48+
49+
.. code-block:: sh
50+
51+
$ gel migration create
52+
did you create object type 'default::Timestamped'? [y,n,l,c,b,s,q,?]
53+
> y
54+
did you alter object type 'default::Card'? [y,n,l,c,b,s,q,?]
55+
> y
56+
did you alter object type 'default::Deck'? [y,n,l,c,b,s,q,?]
57+
> y
58+
Created /home/strinh/projects/flashcards/dbschema/migrations/00004-m1d2m5n.edgeql, id: m1d2m5n5ajkalyijrxdliioyginonqbtfzihvwdfdmfwodunszstya
59+
60+
$ gel migrate
61+
Applying m1d2m5n5ajkalyijrxdliioyginonqbtfzihvwdfdmfwodunszstya (00004-m1d2m5n.edgeql)
62+
... parsed
63+
... applied
64+
65+
.. edb:split-section::
66+
67+
Update the ``get_decks`` query to sort the decks by ``updated_at`` in descending order.
68+
69+
.. code-block:: python-diff
70+
:caption: main.py
71+
72+
@app.get("/decks", response_model=List[Deck])
73+
async def get_decks():
74+
decks = await client.query("""
75+
select Deck {
76+
id,
77+
name,
78+
description,
79+
cards := (
80+
select .cards {
81+
id,
82+
front,
83+
back
84+
}
85+
order by .order
86+
)
87+
}
88+
+ order by .updated_at desc
89+
""")
90+
return decks
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
.. edb:env-switcher::
22
3-
===========
4-
Inheritance
5-
===========
3+
========================
4+
Adding shared properties
5+
========================
66

77
.. toctree::
88
:maxdepth: 3
99
:hidden:
1010

1111
nextjs
12+
fastapi
1213

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
.. _ref_quickstart_fastapi_modeling:
2+
3+
=================
4+
Modeling the data
5+
=================
6+
7+
.. edb:split-section::
8+
9+
The flashcards application has a simple data model, but it's interesting enough to utilize many unique features of the |Gel| schema language.
10+
11+
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.
12+
13+
.. code-block:: python
14+
15+
from pydantic import BaseModel
16+
17+
class CardBase(BaseModel):
18+
front: str
19+
back: str
20+
21+
class Card(CardBase):
22+
id: str
23+
24+
class DeckBase(BaseModel):
25+
name: str
26+
description: Optional[str] = None
27+
28+
class Deck(DeckBase):
29+
id: str
30+
cards: List[Card]
31+
32+
.. edb:split-section::
33+
34+
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.
35+
36+
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.
37+
38+
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.
39+
40+
.. code-block:: sdl-diff
41+
:caption: dbschema/default.gel
42+
43+
module default {
44+
+ type Card {
45+
+ required order: int64;
46+
+ required front: str;
47+
+ required back: str;
48+
+ };
49+
+
50+
+ type Deck {
51+
+ required name: str;
52+
+ description: str;
53+
+ multi cards: Card {
54+
+ constraint exclusive;
55+
+ on target delete allow;
56+
+ };
57+
+ };
58+
};
59+
60+
.. edb:split-section::
61+
62+
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.
63+
64+
To make |Gel| do that, you need to do two quick steps:
65+
66+
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.
67+
68+
.. note::
69+
70+
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.
71+
72+
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.
73+
74+
.. code-block:: sh
75+
76+
$ uvx gel migration create
77+
Created ./dbschema/migrations/00001-m125ajr.edgeql, id: m125ajrbqp7ov36s7aniefxc376ofxdlketzspy4yddd3hrh4lxmla
78+
$ uvx gel migrate
79+
Applying m125ajrbqp7ov36s7aniefxc376ofxdlketzspy4yddd3hrh4lxmla (00001-m125ajr.edgeql)
80+
... parsed
81+
... applied
82+
83+
84+
.. edb:split-section::
85+
86+
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.
87+
88+
.. edb:split-point::
89+
90+
.. code-block:: sh
91+
92+
$ uvx gel ui
93+
94+
.. image:: images/schema-ui.png
Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
.. edb:env-switcher::
22
3-
========
4-
Modeling
5-
========
3+
=================
4+
Modeling the data
5+
=================
66

77
.. toctree::
88
:maxdepth: 3
99
:hidden:
1010

1111
nextjs
12-
13-
12+
fastapi

docs/intro/quickstart/modeling/nextjs.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Modeling the data
88
99
The flashcards application has a simple data model, but it's interesting enough to utilize many unique features of the |Gel| schema language.
1010

11-
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.
11+
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.
1212

1313
.. code-block:: typescript
1414
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
.. _ref_quickstart_fastapi:
2+
3+
==========
4+
Quickstart
5+
==========
6+
7+
Welcome to the quickstart tutorial! In this tutorial, you will update a FastAPI
8+
backend for a Flashcards application to use |Gel| as your data layer. The
9+
application will let users build and manage their own study decks, with each
10+
flashcard featuring customizable text on both sides - making it perfect for
11+
studying, memorization practice, or creating educational games.
12+
13+
Don't worry if you're new to |Gel| - you will be up and running with a working
14+
FastAPI backend and a local |Gel| database in just about **5 minutes**. From
15+
there, you will replace the static mock data with a |Gel| powered data layer in
16+
roughly 30-45 minutes.
17+
18+
By the end of this tutorial, you will be comfortable with:
19+
20+
* Creating and updating a database schema
21+
* Running migrations to evolve your data
22+
* Writing EdgeQL queries
23+
* Building an app backed by |Gel|
24+
25+
26+
Features of the flashcards app
27+
------------------------------
28+
29+
* Create, edit, and delete decks
30+
* Add/remove cards with front/back content
31+
* Clean, type-safe schema with |Gel|
32+
33+
Requirements
34+
------------
35+
36+
Before you start, you need:
37+
38+
* Basic familiarity with Python and FastAPI
39+
* Python 3.8+ on a Unix-like OS (Linux, macOS, or WSL)
40+
* A code editor you love
41+
42+
Why |Gel| for FastAPI?
43+
----------------------
44+
45+
* **Type Safety**: Catch data errors before runtime
46+
* **Rich Modeling**: Use object types and links to model relations
47+
* **Modern Tooling**: Python-friendly schemas and migrations
48+
* **Performance**: Efficient queries for complex data
49+
* **Developer Experience**: An intuitive query language (EdgeQL)
50+
51+
Need Help?
52+
----------
53+
54+
If you run into issues while following this tutorial:
55+
56+
- Check the `|Gel| documentation <https://docs.geldata.com>`_
57+
- Visit our `community Discord <https://discord.gg/gel>`_
58+
- File an issue on `GitHub <https://github.com/geldata/gel>`_

0 commit comments

Comments
 (0)