Skip to content
1 change: 1 addition & 0 deletions docs/intro/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Get Started
:hidden:

quickstart
quickstart/fastapi/index
cli
instances
projects
Expand Down
75 changes: 75 additions & 0 deletions docs/intro/quickstart/fastapi/connecting.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
.. _ref_quickstart_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_client`` function isn't being passed any connection details. How does Gel know how to connect to the database you set up earlier? When we ran ``gel project init`` earlier, the CLI created credentials for the local database and stored them in a well-known location. When you initialize your client with ``create_client()``, Gel will check the places it knows about for 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 when you're ready to deploy, and the client knows what to do!

.. edb:split-point::

.. code-block:: python
:caption: ./test.py

import gel

client = gel.create_client()

result = client.query_single("select 'Hello from Gel!';")
print(result)

.. 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

client = gel.create_client()

- result = client.query_single("select 'Hello from Gel!';")
- print(result)
+ client.query("""
+ insert Deck { name := "I am one" }
+ """)
+
+ client.query("""
+ insert Deck { name := "I am two" }
+ """)
+
+ decks = client.query("""
+ select Deck {
+ id,
+ name
+ }
+ """)
+
+ for deck in decks:
+ print(f"ID: {deck.id}, Name: {deck.name}")
+
+ client.query("delete Deck")


.. 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
71 changes: 71 additions & 0 deletions docs/intro/quickstart/fastapi/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.. _ref_quickstart_fastapi:

############
Quickstart
############

.. toctree::
:maxdepth: 1
:hidden:

setup
modeling
connecting
working
inheritance

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>`_
90 changes: 90 additions & 0 deletions docs/intro/quickstart/fastapi/inheritance.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
.. _ref_quickstart_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
88 changes: 88 additions & 0 deletions docs/intro/quickstart/fastapi/modeling.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
.. _ref_quickstart_modeling:

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

.. edb:split-section::

The flashcards application has a simple data model, but it's interesting enough to get a taste of many of the features of the Gel schema language. You have a ``Card`` class that extends the ``CardBase`` class describing a single flashcard, which for now contains two required string properties: ``front`` and ``back``. Each ``Card`` belongs to a ``Deck``, and there is an explicit ordering to the cards in a given deck.

Looking at the mock data, you can see this structure in the JSON.

.. code-block:: typescript

from pydantic import BaseModel

class CardBase(BaseModel):
order: int
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.

.. 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;
+ };
+ };
};

.. 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:

* **Create a migration**: a file with a list of low-level instructions.

.. 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.

* **Apply the migration**: basically, tell Gel "I want you to use these instructions and get my types ready for action."

.. code-block:: sh

$ gel migration create
Created ./dbschema/migrations/00001-m125ajr.edgeql, id: m125ajrbqp7ov36s7aniefxc376ofxdlketzspy4yddd3hrh4lxmla
$ 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.

.. code-block:: sh

$ gel ui
Loading
Loading