Skip to main content

Exograph supports pgvector for embeddings

· 9 min read
Ramnivas Laddad
Co-founder @ Exograph

We are happy to introduce Exograph's first integration with AI: embedding support through the pgvector extension. This new feature allows storing and querying vector representations of unstructured data directly in your Postgres database. It complements other Exograph features like access control, indexing, interceptors, and migrations, simplifying the development of AI applications.

Embeddings generated by AI models from OpenAI, Claude, Mistral, and others condense the semantic essence of unstructured data into a small-size vector. The distance between two vectors indicates the similarity of the corresponding data. This capability opens up many possibilities, such as building recommendation systems, classifying data, and implementing AI techniques like RAG (Retrieval Augmented Generation).

Exograph's access control mechanism ensures that the search results are filtered based on the user's access rights. For example, when implementing a RAG application, Exograph ensures that it retrieves and feeds the AI model only the documents the user can access. Access control at the data model level eliminates a source of privacy issues in AI applications.

Creating RAG applications with Exograph and pgvector

In this blog post, we will explore the concept of embeddings and how Exograph supports it.

Overview

Exograph's embedding support comes through a new Vector type, which uses the pgvector extension internally. This extension enables storing vectors in the database alongside regular data, thus simplifying the integration of embeddings into your application.

Here is an example of a document module with embedding support for the content field:

@postgres
module DocumentModule {
@access(true)
type Document {
@pk id: Int = autoIncrement()
title: String
content: String
contentVector: Vector
}
}

Once you add a field of the Vector type, Exograph takes care of many aspects:

  • Creating and migrating the database schema.
  • Supporting mutation APIs to store the embeddings in that field.
  • Extending retrieval and ordering APIs to use the distance from the given vector.

Let's explore the concept of embeddings and Exograph's support for it.

What is an Embedding?

Picture this: You aim to compile and search through your organization's documents efficiently. Traditional methods fall short; they merely compare strings, missing nuanced connections. For instance, a search for "401k" won't reveal documents mentioning just "Roth"—even though they both deal with the concept of "retirement savings". Enter embeddings.

Embeddings transform data into a numeric vector, capturing its semantic essence.

A mental model behind embeddings is that each dimension represents a semantics (also referred to as a feature or a concept), and the value in a vector represents its closeness to a particular semantics. Consider words like "car", "motorcycle", "dog", and "elephant". If you were to create a vector representation manually, you may define a few semantics and assign a value based on how well the word fits. For instance, you may define the following semantics: "Transportation", "Heavy", and "Animal". You may then assign a value to each word based on how it fits these features. The table below illustrates this concept:

WordSemantic Dimension
TransportationHeavyAnimal
Car0.90.80.1
Motorcycle0.80.50.1
Dog0.10.10.9
Elephant0.60.90.9

Here, you created a three-dimensional vector for each word.

Car:        [0.9, 0.8, 0.1]
Motorcycle: [0.8, 0.5, 0.1]
Dog: [0.1, 0.1, 0.9]
Elephant: [0.6, 0.9, 0.9]

In practice, you would use an AI model like OpenAI's text-embedding-3-small, which generates the vector based on its training data. The dimension labels in the resulting vector are opaque and lack human-interpretable labels such as "Transportation"; instead, you only have an index and its corresponding value.

Finding similar documents through embeddings involves computing the distance between vectors using metrics like the Euclidean distance or cosine similarity and selecting the closest vectors. For example, if you are looking for documents similar to "Truck", you would compute its vector representation (say, [0.95, 0.9, 0.1]) and find documents with vectors close to it (probably "Car" and "Motorcycle" in the example above).

Using embeddings in your application requires two steps:

  1. When adding or updating a document, compute its vector representation, store the vector representation, and link it to the document.
  2. When searching for similar documents, compute the vector representation of the query, find the closest vectors, and retrieve the associated documents. Typically, you'd sort by vector proximity and select the top matches.

Exograph helps with these steps, simplifying AI integration into your applications.

Embeddings in Exograph

Exograph introduces a new type Vector. Fields of this type provide the ability to:

  • Store and update the vector representation.
  • Filter and order based on the distance from the provided value.
  • Specify parameters such as vector size, indexing, and the distance function to compute similarity.

The Vector type feature plays well with the rest of Exograph's capabilities. For example, you can apply access control to your entities, so searching and sorting automatically consider the user's access rights, thus eliminating a source of privacy issues. You can even specify field-level access control to, for example, expose the vector representation only to privileged users.

Let's use the Document model shown earlier but with a few annotations to control a few key aspects:

@postgres
module DocumentModule {
@access(true)
type Document {
@pk id: Int = autoIncrement()
title: String
content: String

@size(1536)
@index
@distanceFunction("l2")
contentVector: Vector?
}
}

First, note that the contentVector field is of the Vector type is marked optional. This supports the typical pattern of initially adding documents without embedding and adding vector representation asynchronously.

Next, note the annotations for the contentVector field to specify a few key aspects:

  • Size: By default, Exograph sets up the vector size to 1536, but you can specify a different size using the @size annotation. Exograph's schema creation and migration will factor in the vector size.

  • Indexing: Creating indexes speeds up the search and ordering. When you annotate a Vector field with the @index annotation, during schema creation (and migration), Exograph sets up a Hierarchical Navigable Small World (HNSW) index.

  • Distance function: The core motivation for using vectors is to find vectors similar to a target. There are multiple ways to compute similarity, and based on the field's characteristics, one may be more suitable than others. Since it is a field's characteristic, you can annotate Vector fields using the @distanceFunction annotation to specify the distance function. By default, Exograph uses the "cosine" distance function, but you can specify the "l2" distance function (L2 or Euclidean distance) or "ip" (inner product). Exograph will automatically use this function when filtering and ordering. It will also automatically factor in the distance function while setting up the index.

Access control

We use a wide-open access control policy (@access(true)) to keep things simple. In practice, you would use a more restrictive access control policy to ensure only authorized users can access the document's content and vector representation. For example, you could introduce the notion of a "document owner" and allow access only to the owner or users with specific roles (see Evolving Access Control with Exograph for more details). This way, you can ensure that the search results are filtered based on the user's access rights, and when used as context in AI applications, the generated content is based on the user's access rights.

Let's see how to use the Vector type in Exograph from the GraphQL API.

Embedding in GraphQL

Once the model is defined, you can use the Exograph GraphQL API to interact with the model.

To insert a document and its vector representation, you can use the following mutation (updating the vector representation is similar):

mutation ($title: String!, $content: String!, $contentVector: [Float!]!) {
createDocument(
data: { title: $title, content: $content, contentVector: $contentVector }
) {
id
}
}

Note how the Vector field surfaces as a list of floats in the APIs (and not as an opaque custom scalar). This design choice simplifies the integration with the AI models that produce embedding and client code that uses vectors.

Now, we can query our documents. A common query with embedding is to retrieve the top matching documents. You can do it in Exograph with the following query:

query topThreeSimilar($searchVector: [Float!]!) {
documents(
orderBy: { contentVector: { distanceTo: $searchVector, order: ASC } }
limit: 3
) {
id
title
content
}
}

Limiting the number of documents is often sufficient for a typical search or RAG application. However, you can also use the similar operator to filter documents based on the distance from the search vector:

query similar($searchVector: [Float!]!) {
documents(
where: {
contentVector: {
similar: { distanceTo: $searchVector, distance: { lt: 0.5 } }
}
}
) {
id
title
content
}
}

You can combine the orderBy and where clauses to return the top three similar documents only if they are within a certain distance:

query topThreeSimilarDocumentsWithThreshold(
$searchVector: [Float!]!
$threshold: Float!
) {
documents(
where: {
contentVector: {
similar: { distanceTo: $searchVector, distance: { lt: $threshold } }
}
}
orderBy: { contentVector: { distanceTo: $searchVector, order: ASC } }
limit: 3
) {
id
title
content
}
}

You can combine vector-based queries with other fields to filter and order based on other criteria. For example, you can filter based on the document's title along with a similarity filter and order based on the distance from the search vector:

query topThreeSimilarDocumentsWithTitle(
$searchVector: [Float!]!
$title: String!
$threshold: Float!
) {
documents(
where: {
title: { eq: $title }
contentVector: {
similar: { distanceTo: $searchVector, distance: { lt: $threshold } }
}
}
orderBy: { contentVector: { distanceTo: $searchVector, order: ASC } }
limit: 3
) {
id
title
content
}
}

These filtering and ordering capabilities make it easy to focus on the business logic of your application and let Exograph handle the details of querying and sorting based on vector similarity.

What's Next?

This is just the beginning of empowering Exograph applications to leverage the power of AI with minimal effort. We will continue to enhance this feature to support more AI models.

For more detailed documentation, please see the embeddings documentation.

We are excited to see what you build with this new capability. You can reach us on Twitter or Discord with your feedback.

Share:

Exograph supports trusted documents

· 7 min read
Ramnivas Laddad
Co-founder @ Exograph

Exograph 0.7 introduces support for trusted documents, also known as "persisted documents" or "persisted queries". This new feature makes Exograph even more secure by allowing only specific queries and mutations, thus shrinking the API surface. It also offers other benefits, such as improving performance by reducing the payload size.

Trusted documents are a pre-arranged way for clients to convey "executable documents" (roughly queries and mutations, but see the GraphQL spec and Exograph's documentation for a distinction) they would be using. The server then allows executing only those documents.

tip

For a good introduction to trusted documents, including the reason to prefer the term "trusted documents", please see the GraphQL Trusted Documents. Exograph's trusted document follows the same general principles outlined in it. We hope that someday a GraphQL specification will standardize this concept.

This blog post explains what trusted documents are and how to use them in Exograph.

Conceptual Overview

Why Trusted Documents?

Consider the todo application we developed in an earlier blog (source code). This single-screen application allows creating, updating, and deleting todos besides viewing by their completion status. The application only needs the following queries and mutations (the application uses fragments, but we will keep it simple here).

The client application needs two queries to get todos by their completion status:

Query to get todos by completion status
query getTodosByCompletion($completed: Boolean!) {
todos(where: { completed: { eq: $completed } }, orderBy: { id: ASC }) {
id
completed
title
}
}
Query to get all todos
query getTodos {
todos(orderBy: { id: ASC }) {
id
completed
title
}
}

The application also needs mutations to create, update, and delete a todo:

Mutation to create a todo
mutation createTodo($completed: Boolean!, $title: String!) {
createTodo(data: { title: $title, completed: $completed }) {
id
}
}
Mutation to update a todo
mutation updateTodo($id: Int!, $completed: Boolean!, $title: String!) {
updateTodo(id: $id, data: { title: $title, completed: $completed }) {
id
}
}
Mutation to delete a todo
mutation deleteTodo($id: Int!) {
deleteTodo(id: $id) {
id
}
}

Contrast this with what the server offers. For example, the server provides:

  • Querying a todo by its ID, but the client application doesn't need it, since it doesn't display a single todo.
  • Flexible filtering, but the client application needs only by completion status.
  • Sorting by other fields or a combination, but the client application only sorts by the ID in ascending order.
  • Creating, updating, and deleting in bunk, but the client application only mutates one todo at a time.

While Exograph (and other GraphQL servers) offer flexible field selection for all queries and mutations, each client query or mutation only needs a specific set of fields.

What are Trusted Documents?

Because the client app requires only specific queries and mutations, it can inform the server using trusted documents. These documents are files with all the executable queries and mutations the client app needs. For instance, in the case of the todo app, a tool would create a file like this:

{
"2a...": "query getTodosByCompletion($completed: Boolean!) ...",
"3b...": "query getTodos ...",
"4c...": "mutation createTodo($completed: Boolean!, $title: String!) ...",
"5d...": "mutation updateTodo($id: Int!, $completed: Boolean!, $title: String!) ...",
"6e...": "mutation deleteTodo($id: Int!) ..."
}

Here, the key is a hash (SHA256, for example) of the query or mutation, and the value is the query or mutation itself.

Benefits of using Trusted Documents

Using trusted documents reduces the effective API surface area and offers several benefits, such as:

  • Preventing attackers from executing arbitrary queries and mutations.
  • Reducing the bandwidth by sending only the hash of the query instead of the query itself.
  • Providing a focus for testing the actual queries and mutations used by the client application.
  • Allowing server-side optimizations such as caching query parsing and pre-planning query execution.

You may wonder if there is a different way to achieve the same benefits. Specifically, couldn't the server provide a narrower set of queries and mutations? For example, could it not offer the API "get a todo by its ID"? While feasible, this approach undermines one of GraphQL's strengths: enabling client-side development without continuous server team involvement. For example, the client application may want to offer a route such as todos/:id to show a particular todo. It will then need a todo by its ID. Without this query, the client must request the server team to add it, consuming time and resources. Trusted documents streamline this process. They allow the client to effortlessly express requirements, like fetching todos by ID. Thus, the server should maintain a reasonably flexible API while clients communicate their needs through trusted documents.

Using Trusted Documents in Exograph

Like any other feature in Exograph, using trusted documents is straightforward. All you need to do is put the trusted documents in the trusted-documents directory! Exograph will automatically use them.

Workflow

A typical workflow to use trusted documents in Exograph is as follows:

  1. Generate trusted documents: In this (typically automated) step, a tool such as graphql-codegen and generate-persisted-query-manifest examines the client application and generates the trusted documents.
  2. Convey the server: Exograph expects trusted documents in either format in the trusted-documents directory. The server will now accept only the trusted documents.
  3. Set up the client: The client may now send the hashes of the executable documents instead of the full text. To do this, The client can use the persistedExchange with URQL or @apollo/persisted-query-lists or some custom code.

For a concrete implementation of this workflow, please see the updated todo application: Apollo version and URQL version.

Organizing Trusted Documents

Typically, you will have more than one client, each with a different set of trusted documents, connected to an Exograph server. Exograph supports this by allowing them to be placed anywhere in the trusted-documents directory. For example, you could support multiple clients and their versions by placing trusted documents in the following structure:

todo-app
├── src
│ └── ...
├── trusted-documents
│ ├── web
│ │ └── user-facing.json
│ │ └── admin-facing.json
│ └── ios
│ │ └── core.json
│ │ └── admin.json
│ └── android
│ │ └── version1.json
│ │ └── version2.json

The server will allow trusted documents in any of the files.

Using Trusted Documents in Development

If you have added the trusted-documents directory, Exograph enforces trusted documents in production mode without any way to opt out, extending its general secure by default philosophy.

In development mode, however, where exploring API is common, Exograph makes a few exceptions. When you start the server in development mode (either exo dev or exo yolo), it allows untrusted documents under the following conditions:

  • If the GraphQL playground executes an operation.
  • If the development server is stated with the --enforce-trusted-documents=false option.

In either case, it will implicitly trust any introspection query, thus allowing GraphQL Playground and tools such as GraphQL Code Generator to work.

Try it out

You can see this support in action through the updated todo application: Apollo version and URQL version.

For more detailed documentation, including how to create trusted documents and set up the client, please see the trusted documents documentation.

Let us know what you think of trusted documents support in Exograph. You can reach us on Twitter or Discord.

Share:

Riding on Railway

· 5 min read
Ramnivas Laddad
Co-founder @ Exograph

The driving principle behind Exograph is to make it easy for developers to build their backends by letting them focus only on inherent—not incidental—complexity; Exograph should handle everything else. With Exograph, you can create a GraphQL server with a rich domain model with authorization logic, Postgres persistence, and JavaScript/TypeScript business logic in just a few lines of code. But what about deploying to the cloud?

The new platform-as-a-service offerings make deploying Exograph apps easy, and we make it easier by providing specific integrations. In this blog, I will focus on Railway. With its Postgres support and GitHub integration, you can create an Exograph server from scratch and deploy it in under three minutes!

A quick overview of Exograph

Let's take a quick look at Exograph from a deployment perspective, which will make the Railway integration easy to understand. Exograph separates the build and execution steps and ships two corresponding binaries: exo and exo-server.

The exo binary Exograph's cli tool takes care of everything from building the app, migrating schema, and running integration tests to acting as a development server. It also simplifies deployment to cloud platforms, as we will see shortly.

Running an Exograph application requires that you build the app using exo build and then run the server using exo-server. The exo build command processes the index.exo file (and any files imported from it) and bundles JS/TS files. The result is an intermediate representation file: index.exo_ir.

Building an Exograph app
exo build

The exo-server binary' is the runtime, whose sole purpose is to run Exograph apps. It processes the index.exo_ir file and runs the server.

Running an Exograph app
exo-server

Please see the Exograph Architecture for more details.

These tools are available as Docker images, making it easy to integrate with cloud platforms and form the core of our Railway integration.

Railway Integration

The exo deploy railway command generates a Docker file (Dockerfile.railway) and railway.toml to point to this file. The generated Docker file contains two stages:

  1. Build stage: Build the app and another to run it. The build stage uses the exo Docker image to build the app and then copies the result to the runtime stage. The stage also runs database migrations.
  2. Runtime stage: Runs the server. It uses the exo-server Docker image.

Deploying to Railway then involves:

  • Pushing all the code to GitHub (or using railway up to push the code directly to Railway)
  • Creating a Railway project
  • Creating a Postgres service (unless you use an external Postgres)
  • Creating a new service pointing to the GitHub repo (unless you use railway up)
  • Binding environment variables for the Postgres database to the service

Let's see how this works in practice.

Creating a new Exograph project

This is easy!

exo new todo
cd todo

This creates a new project with a simple todo app and initializes Git. If you are wondering, this doesn't generate much code. Here is the entire model!

@postgres
module TodoDatabase {
@access(true)
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
}
}

If you would like, you can run exo yolo to try it out locally.

Deploying to Railway

We have a choice of using Postgres offered by Railway or an external one. Let's look at both options.

Using its Postgres

Railway offers the Postgres database service, which has the advantage of keeping both the Exograph app and the database on the same platform.

exo deploy railway --use-railway-db=true

This will generate files necessary to deploy to Railway and provide step-by-step instructions. Here is a video of the entire process: we create an Exograph project from scratch and deploy it to Railway in under three minutes.

note

Currently, the process involves using the dashboard to create the Postgres database and binding it to the service. We will keep an eye on Railway's tooling to make this easier. For example, if Railway supports Infrastructure-as-Code (a requested feature), we can lean on it to make the whole process in a single command.

Using Neon's Postgres

Using an external Postgres database is also easy and has the advantage of specialization offered by database providers. We will use Neon for this example.

Passing --use-railway-db=false to exo deploy railway will generate files for use with an external Postgres database.

exo deploy railway --use-railway-db=false

Other than using an external Postgres, the process is the same as using Railway's Postgres.

Using the playground

Following the best practice, we do not enable introspection and playground in production. But that makes working with the GraphQL server challenging. Exograph's playground solves this problem by using schema from the local model (the app's source code) and sending requests to the remote server. This way, you can explore the API without enabling introspection and playground in production.

Let us know what you think of our Railway integration. You can reach us on Twitter or Discord.

Share:

What's new in Exograph 0.4

· 5 min read
Ramnivas Laddad
Co-founder @ Exograph
Shadaj Laddad
Co-founder @ Exograph

We are excited to announce the release of Exograph 0.4! This release introduces several new features and enhancements, many in response to our users' feedback (thank you!). While we will explore some of these features in future blogs, here is a quick summary of what's new since the 0.3 version.

NPM modules support

Exograph offers a Deno integration to write custom business logic in TypeScript or JavaScript. Until now, you could only use Deno modules. While Deno modules ecosystem continues to expand, it is not as rich as the Node ecosystem. Recognizing this, Deno added support for NPM modules to tap into the vast range of NPM packages, and Exograph 0.4 extends this support through our integration. You can now harness NPM modules to checkout with Stripe, send messages with Slack, interact with AWS, and so on.

As an illustration, here is how you would send emails using Resend. First, you would define a mutation:

@deno("email.ts")
module Email {
@access(true)
mutation sendAnnouncement(): Boolean
}

And implement it using the resend NPM module:

import { Resend } from "npm:resend";

const resend = new Resend("re_...");

export async function sendAnnouncement(): Promise<boolean> {
await resend.emails.send({
from: "...",
to: "...",
subject: "Exograph 0.4 is out!",
html: "<p>Exograph <strong>0.4</strong> is out with support for npm modules, playground mode, and more!</p>",
});

return true;
}

Compared to the example shown on the Resend site, the difference is the npm: prefix in the import statement. This prefix tells Exograph to look for the module in the npm registry.

Railway integration

Exograph's deployment has always been easy since the server is just a simple binary with everything needed to run. However, we strive to make it easier for specific platforms. Exograph 0.4 now supports Railway as a deployment target! Here is a quick video where we create an Exograph project from scratch and deploy it to Railway in under three minutes.

To support this kind of integration, where the cloud platform can also run the build step, we now publish two Docker images: cli with the exo binary and server with the exo-server binary.

exo playground

A recommended practice for GraphQL deployments is to turn off introspection in production, which is the default in Exograph. However, exploring the GraphQL API with such a server becomes difficult without code completion and other goodies. Exograph 0.4 introduces a new exo playground command that uses the schema from the local server and executes GraphQL operations against the specified remote endpoint.

exo playground --endpoint https://<server-url>/graphql
Starting playground server connected to the endpoint at: https://<server-url>/graphql
- Playground hosted at:
http://localhost:9876/playground

You will see a UI element in the playground showing the specified endpoint. Besides this difference, the playground will behave identically to the local playground, including autocomplete, schema documentation, query history, and integrated authentication.

note

This doesn't bypass the recommended practice of turning off introspection in production. The exo playground is useful only if you have access to the server's source code, in which case, you would know the schema, anyway!

See the video with the Railway integration above for a quick demo of the exo playground command.

Access control improvements

Exograph offers to express access control rules precisely. In version 0.4, we enhanced this expressive power through higher-order functions. Consider a document management system where users can read documents if they have read permissions and mutate if they have written permissions. The following access control rules express this requirement.

context AuthContext {
@jwt("sub") id: Int
}

@postgres
module DocsDatabase {
@access(
query = self.permissions.some(permission => permission.user.id == AuthContext.id && permission.read),
mutation = self.permissions.some(permission => permission.user.id == AuthContext.id && permission.write)
)
type Document {
@pk id: Int = autoIncrement()
...
permissions: Set<Permission>
}

@access(...)
type Permission {
@pk id: Int = autoIncrement()
document: Document
user: User
read: Boolean
write: Boolean
}

@access(...)
type User {
@pk id: Int = autoIncrement()
...
permissions: Set<Permission>
}
}

With this setup, no user can read or write a document without the appropriate permission. The some function allows you to express this requirement in a single line. Internally, it lowers it down to an SQL predicate for efficient execution.

Currently, we support the some higher-order function, which matches the Array.prototype.some function in Javascript. This function takes a predicate function and returns true if the predicate function returns true for any element in the array.

Other improvements

Besides these major features, we continue to improve Exograph to fit more use cases and simplify the developer experience.

For the Postgres plugin, for example, you can now specify that the tables could be in a non-public schema and specify deeply nested creating and updates in a single mutation. It also allows clients to supply the primary key value for UUID fields when creating an entity.

In version 0.3, we introduced a friction-free integration with Clerk for authentication. Since then, we have extended this support to Auth0 as an authentication provider! While Exograph generically supports all compliant OIDC providers, the playground integration for Clerk and Auth0 makes it easy to try out APIs that require authentication. Along the way, we updated key rotation for the OIDC authentication mechanism.

Let us know what you think of these new features and what you would like to see in the future. You can reach us on Twitter or Discord.

Share:

Authentication in Exograph with Auth0

· 4 min read
Ramnivas Laddad
Co-founder @ Exograph

On the heels of Clerk integration, we are excited to announce that Exograph now supports Auth0 as an authentication provider! Exograph's JWT support seamlessly works with Auth0 out of the box. Additionally, Exograph's playground integrates Auth0's authentication UI to simplify the exploration of access control rules.

Our code will be the same as in the previous blog. Since both Clerk and Auth0 support OpenID Connect (OIDC), everything can stay the same.

context AuthContext {
@jwt("sub") id: String
}

@postgres
module TodoDatabase {
@access(self.userId == AuthContext.id)
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
userId: String = AuthContext.id
}
}

This is all the code you need for a multi-user todo app! With the rule specified in the @access annotation, each user can only query or mutate their todos.

note

Exograph's @jwt annotation works with any compliant OIDC provider, so you may use it with any other provider. However, the playground integration is currently only available with Clerk and Auth0.

To try it out, create an Auth0 project following their instructions. Pay particular attention to configuring "Allowed Callback URLs" (for this blog, you may set it to http://localhost:9876/playground, http://localhost:3000).

Then you can start the server using exo yolo with the EXO_OIDC_URL environment variable set to your Auth0 URL:

EXO_OIDC_URL=https://<your-auth0-host>.auth0.com exo yolo

This will start the server with Auth0 as the authentication provider.

Auth0 integration with Exograph Playground

A typical workflow for building an app uses the GraphQL API in the playground to try out queries and mutations needed for the frontend and copy those into the frontend code. However, in the presence of authentication, grabbing a JWT token (typically from a partially built UI) and passing it in the Authorization header can be cumbersome. Exograph Playground makes it easy to try out APIs that require authentication by integrating Auth0's UI in the playground.

Try it out. For example, you can execute the following query to get all todos:

query {
todos {
id
title
completed
}
}

If you do so without logging in, you will get an error:

{
"errors": [
{
"message": "Not authorized"
}
]
}

You can log in by clicking the "Authenticate" button in the playground. This will open Auth0's login page in the playground. You can log in using any configured provider (such as Google or Facebook). Once logged in, you can try the query again, and you will see the todos.

Similarly, you can execute mutations. For example, the following mutation will create a new todo:

mutation {
createTodo(data: { title: "Buy milk", completed: false }) {
id
}
}

Due to access control rules, you can create a todo only for the logged-in user.

Lastly, try out the query above by logging in as another user. You will see that the todos created by the first user are not visible to the second user.

Using the frontend

With the confidence that the API works as expected, building a frontend using the same queries and mutations is easy. The accompanying source code is a Next.js app that allows creating todos, marking them as completed, and updating or deleting them.

Clone the examples repository and try it out!

Summary

Combining the declarative backend of Exograph with the authentication of Auth0 is a simple matter of setting the EXO_OIDC_URL environment variable. The playground support makes it a breeze to try out various authentication scenarios to ensure that users access only the data they are supposed to.

We would love to hear your feedback on this integration on our Discord.

Share:

Fortifying Exograph with Clerk

· 6 min read
Ramnivas Laddad
Co-founder @ Exograph

In a previous blog, our code used Google Identity for authentication. While such a solution is possible, taking care of details, such as supporting multiple social providers or password-less authentication, verifying emails, and refreshing/revoking tokens, can get tedious. This is where external authentication providers such as Clerk make life easier.

Exograph now supports Clerk as an authentication provider! Our integration goes beyond supporting JWKS authentication; it also makes it easy and fun to explore APIs by integrating Clerk's UI in Exograph's playground.

This blog will transform a todo app without authentication into a multi-user todo app by adding just four lines and modifying a single line of code! The accompanying source code includes a web frontend written in Next.js, Tailwind, Apollo GraphQL Client, and TypeScript.

A taste of Clerk integration

Let's start with the same first step as the previous blog, with the code generated by the exo new command for a single-user todo project:

@postgres
module TodoDatabase {
@access(true)
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
}
}

If you start the server using exo yolo, you can explore the API in the playground to query, create, update, and delete todos.

To make this a multi-user app, we associate each Todo with a user and ensure that a user can only access their todos through the following changes:

context AuthContext {
@jwt("sub") id: String
}

@postgres
module TodoDatabase {
@access(self.userId == AuthContext.id)
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
userId: String = AuthContext.id
}
}

That's all we need to change! Compare this to other approaches where you would have to change most SQL statements or update invocation methods of an underlying framework besides including boilerplate code to deal with JWT verification.

Let's see what we did:

  • A new AuthContext sources the user id from the JWT token's sub claim (which will be the same as Clerk's user id). The client will send the JWT token in the Authorization header, and Exograph will verify it using the API URL provided by Clerk.
  • The Todo type adds a userId field. This field defaults to AuthContext's id value to let the client skip the user id when creating a todo.
  • The access control rule enforces that users may query and mutate Todos only if their userId matches AuthContext's id.
A difference from the previous blog

In the previous blog, we also had the User type, but since Clerk deals with user management, we don't need that anymore.

Now that the backend is ready, let's explore the API.

Using the playground

In a typical setup, the client will authenticate and send the Authorization token with each request. But how can you explore the API in the playground? How can you get a JWT token to put in the Authorization header? Developers often copy and paste the JWT token from the frontend app into the playground, which is a tedious process, to say the least (and requires building at least some frontend). It becomes even more challenging with providers such as Clerk, where the token expires quickly (by default, in 60 seconds).

A common problem

This is neither specific to GraphQL nor Clerk. The same issue arises with REST APIs, gRPC, etc., and any authentication mechanism, including roll-your-own.

Exograph integrates its playground with Clerk authentication (as well as roll-your-own, but that is not the focus of this blog). The playground includes the signing-in mechanism and automatically sends the Authorization header with the JWT for the signed-in user for each GraphQL request. In other words, the playground acts as an alternative frontend. You can now easily explore the API by, for example, signing in as different users and seeing how the access control rules work.

Let's try it out:

  • Set up a Clerk project by following the instructions and note down the value for "Publishable key" and "JWT public key/JWKS URL" from the Clerk dashboard under the "API Keys" section.
  • Start the Exograph server by specifying the OIDC URL as an environment variable. We will use the yolo command to start the server (you can also use the dev or production mode. See Exograph's documentation for more details).
EXO_OIDC_URL=https://<your-clerk-host>.clerk.accounts.dev exo yolo
  • In the playground, click on the key icon in the middle of the screen. The first time you open it, it will ask for the "Publishable key" you noted earlier. You may specify the template id but don't need it for this application. If you want to change this information, you can do it through the "Configure Clerk" link in the sign-in dialog.
  • You will see the sign-in dialog once you specify the publishable key.
  • Once you sign in, you will see the user's profile picture near the key icon to indicate the currently signed-in user.

Try various queries and mutations. For example, create a todo:

mutation {
createTodo(data: { title: "Buy milk", completed: true }) {
id
title
completed
}
}

Or query the todos:

query {
todos {
id
title
completed
}
}

Please see the earlier blog for a list of queries and mutations to try.

Now log out and try the same queries and mutations. You will get an error when you try to create a todo (unauthenticated users can't create todos). You will also see that you don't see any todos when you query them (users can see only their todos). Login as another user; as you would expect, you won't see the todos created by the first user.

Using the frontend

Once you have explored the API, building a frontend using the same queries and mutations is easy. The accompanying source code is a Next.js app that allows creating todos, marking them as completed, and updating or deleting them.

You can sign in as different users and see how the access control rule works (one user will not be able to see or change another user's todos).

Summary

Combining the declarative backend of Exograph with the authentication of Clerk was a breeze. It took us just four lines of code and a single-line modification to the model to make the todo app multi-user. The integration with Clerk's UI in the playground makes it easy to explore the API without building a frontend.

There is more that we didn't explore here, such as using custom templates. For example, we could use these to implement role-based access control to allow admins to see all todos. All we would have to change is add one line to the context (@jwt role: String) and modify another to add || AuthContext.role == "admin" to the access control rule! Such is the power of Exograph's declarative modeling and access control expressions. Please see the documentation for more details.

We would love to hear your feedback on this integration on our Discord.

Share:

Mercury Retrograde: Access Control with a Planetary Twist

· 4 min read
Ramnivas Laddad
Co-founder @ Exograph

Mercury going retrograde is a serious affair, at least according to Reddit discussions. Mercury being the God of communication, supposedly, its (apparent) retrograde movement causes communication breakdowns leading to network outages, server crashes, data loss, and so on. Some take it quite seriously. There is even a Zapier integration to notify you in Slack when Mercury is about to retrograde. Well, it is about to happen tomorrow! So let me hurry up and publish this post before it is too late 😄.

The astronomical explanation for Mercury retrograde

Astronomically, it is an interesting phenomenon, quite nicely visualized here.

With a good explainer here.

In the previous blog, we dealt with access control to express the ownership relation: a todo must be accessible to only those who own it, with some role-based exceptions. However, sometimes you need to go beyond role-based and ownership-based rules. Rather than supporting specific authorization policies that eventually prove too limiting, Exograph takes a different approach through its context mechanism. This mechanism opens the door to implementing even esoteric rules, such as "no deployment when Mercury is retrograde".

Setting up the context

Implementing such a rule in Exograph requires introducing a new context whose field represents Mercury's retrograde status. In Exograph, a context represents information extracted from the incoming request (headers, cookies, IP address) and the environment variables. Exograph includes a general mechanism where users can write custom code to populate the context. That is what we will use to implement the Mercury retrograde rule.

To implement custom context extraction logic, you need to:

  • Write a GraphQL query to return the computed value of interest (in our case, whether Mercury is retrograde)
  • Use the @query annotation to bind the value of the query to a context field (in our case, a boolean field)

Let's start by writing TypeScript code to determine if Mercury is retrograde. We will use https://mercuryretrogradeapi.com/ for that (quite an interesting service deployed to 5+ geographically distributed servers!).

astro.ts
export function isMercuryRetrograde(): bool {
let result = await fetch("https://mercuryretrogradeapi.com");
let json = await result.json();
return json.is_retrograde;
}

Then, we need to bind this query to a context field. We will use the @query annotation to do that.

index.exo
@deno("astro.ts")
module AstroModule {
query isMercuryRetrograde(): bool
}

context AstroContext {
@query("isMercuryRetrograde") mercuryRetrograde: bool
}

Here, the AstroModule exposes a single isMercuryRetrograde query that returns a boolean value. We use this query to populate the mercuryRetrograde context variable.

Expressing access control rules

Finally, let's write an example resource—Deployment—that we want to protect against mercury's shenanigans. We do now want any mutations to Deployment when Mercury is retrograde.

index.exo
@postgres
module DeploymentModule {
@access(query = true, mutation = !AstroContext.mercuryRetrograde)
type Deployment {
@pk id: Int = autoIncrement()
version: String
}
}
Source Code

Play along by cloning the examples repository.

Here, we allow anyone to query the deployment. But we disallow mutations when Mercury is retrograde (you could combine this with other rules, for example, to restrict mutations to admin users only).

Testing the rule

Let's start the server:

exo yolo

You will see output similar to this:

...
Started server on localhost:9876 in 5.35 ms
- Playground hosted at:
http://localhost:9876/playground
- Endpoint hosted at:
http://localhost:9876/graphql

Visit the playground URL and try to create a new deployment:

mutation {
createDeployment(data: { version: "1" }) {
id
}
}

If you try it while Mercury is not retrograde, it will succeed.

{
"data": {
"createDeployment": {
"id": 1
}
}
}

But if you try it while Mercury is retrograde (the next such thing will happen between August 23, 2023 and September 14, 2023), you will get an error:

{
"errors": [
{
"message": "Not authorized"
}
]
}

The same thing will happen if you try to update or delete the deployment:

mutation {
updateDeployment(id: 1, data: { version: "2" }) {
id
version
}
}
mutation {
deleteDeployment(id: 1) {
id
}
}

There is no escaping the access rule!

Conclusion

This fun little exercise shows how Exograph can be used to implement flexible access control rules. Besides built-in context sources, such as the JWT token, header, cookie, and environment variable, Exograph allows sourcing rules from a query implemented using Typescript/JavaScript running in Deno. This separation of context extraction logic from access control expressions enables easy implementation of even weird rules in Exograph.

What's next? Does someone want to implement "No Friday the 13th" rule? Or "No full moon" rule? Or "No deployment on the day of the week that ends with 'y'" rule? Exograph got you covered!

Share:

Evolving Access Control with Exograph

· 9 min read
Ramnivas Laddad
Co-founder @ Exograph
Luke Taylor
Co-founder @ Exograph

Applications, like ideas, begin small and grow over time. Initially, an app might be a single-user demo for your next unicorn startup. If it resonates with your user base, the natural progression involves scaling it to accommodate multiple users. At this point, you need authentication, access control, and administrative functionality.

Adapting your domain model to new requirements is essential and Exograph provides robust support for this process. As your domain evolves, you can easily update the Exograph model to match it. As the model changes, the GraphQL API is automatically synchronized with the model. Exograph also helps manage database migrations. As changes in access control demands arise, you can easily represent them in your model. Additionally, Exograph enables declarative testing, minimizing the risk of regressions as adaptations unfold.

In this blog, we will focus on how Exograph streamlines the evolution of access control rules. We will develop a todo app from scratch starting with a single-user application with no authentication. We will then migrate it into a multi-user platform—complete with authentication and access control. We will see how Exograph makes this transition seamless.

The accompanying code (simple, with authentication) includes a frontend implemented with Next.js, Tailwind, Apollo GraphQL Client, and TypeScript.

Building the single-user version

Let's create an app with no authentication and wide-open access control. Running the following commands is all we need to create the backend:

Creating the project

mkdir todo-app
cd todo-app
exo new api

The starter code defines a single type Todo with a permissive @access(true) policy that allows anyone to query or mutate todos. This access control rule may be acceptable for a local single-user todo app, but not much beyond that. We will fix that in the next section.

@postgres
module TodoDatabase {
@access(true)
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
}
}

Now let's start the backend to explore the GraphQL API:

cd api
exo yolo

The server is now ready 🚀.

Exploring the API

Our goal is to build a frontend that allows us to create, update, and delete todos. It also shows todos by their completion status. Let's see how we can achieve this with the GraphQL API.

Open http://localhost:9876/graphql to try the GraphQL playground.

Creating todos

Let's create a todo. We can do that with the following mutation:

mutation createTodo($title: String!, $completed: Boolean!) {
createTodo(data: { title: $title, completed: $completed }) {
id
title
completed
}
}

Paste the above code in the playground. In the Variables tab, add the following JSON:

{
"title": "Buy milk",
"completed": false
}

Now, execute the mutation by clicking on the Play button. You should see the following response:

{
"data": {
"createTodo": {
"id": "1",
"title": "Buy milk",
"completed": false
}
}
}

Creating todos Creating todos

Create a couple more todos with different titles and completion status.

Updating todos

The frontend will also need to update todos. We can achieve this through the following mutation:

mutation updateTodo($id: Int!, $title: String, $completed: Boolean) {
updateTodo(id: $id, data: { title: $title, completed: $completed }) {
id
title
completed
}
}

Note that the id is required, but title and completed are optional (since we may want to update either). Try updating the title and completion status of a few todos.

Deleting todos

Deleting a todo is easy with the following mutation:

mutation deleteTodo($id: Int!) {
deleteTodo(id: $id) {
id
}
}

Listing todos

Getting all todos is trivial:

query allTodos {
todos {
id
title
completed
}
}

Getting todos by their completion status isn't much harder:

query todosByCompletionStatus($completed: Boolean!) {
todos(where: { completed: { eq: $completed } }) {
id
title
completed
}
}

This is the complete set of GraphQL operations we need to build the frontend. At this point, you may want to check out the repository and run the frontend to see how it works.

Let's move on to the next section to see how to evolve this app to support multiple users.

The multi-user version

The declarative nature of Exograph makes it easy to evolve the model and access control rules as the application grows. In our application, we need to:

  • Add a User type and introduce a relationship between Todo and User. Adding a relationship in Exograph is as easy as adding a field.
  • Add access control such that:
    • Users can only query or mutate todos that they own. However, we will make an exception for the omnipotent admins to do anything.
    • Only admins can query or mutate users.
Authentication

In this blog, we do not focus on the authentication mechanism. The accompanying code, however, includes integration with Google Identity.

Updating the model

We can express the above requirements by updating the model as follows:

context AuthContext {
@jwt("sub") id: Int
@jwt("role") role: String
}

@postgres
module TodoDatabase {
@access(self.user.id == AuthContext.id || AuthContext.role == "admin")
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
user: User = AuthContext.id
}

@access(AuthContext.role == "admin")
type User {
@pk id: Int = autoIncrement()
@unique email: String
firstName: String
lastName: String
profileImageUrl: String
role: String = "user"
todos: Set<Todo>?
}
}

Let's unpack the changes we have made:

  1. Introduce the User type: We introduce a User type to represent the owner of todos (#17).
  2. Establishing relationships: User needs to own Todos, so we set up a one-to-many relationship with Todo by adding a todos field (#24). We also add a user field to Todo to establish the reverse relationship (#13).
  3. Establishing context: We need to know who is accessing the APIs. Therefore, we introduce AuthContext with the id and role fields sourced from the incoming JWT token (#1-#4).
  4. Securing todos: Since we want each Todo to be accessed only by its owner, we express this using self.user.id == AuthContext.id. Here self refers to the current object, which is a Todo. We also added || AuthContext.role == "admin" to allow admins to do anything (#8).
  5. Securing users: The rule for User is even simpler: only admins can query or mutate users (#16).
  6. Setting defaults: Notice the default value of the user is set to AuthContext.id (#13). This allows the user to create a todo without explicitly specifying the user argument to the createTodo mutation (Exograph will automatically associate the current user with the created todo).This also ensures that the queries and mutations used in the single-user version will continue to work unchanged (where specifying the user wasn't even an option). We also set the role field to "user" by default (#23).

Exploring the API

Let's try it out. Exograph's playground makes it easy to create authorization tokens for various users to test scenarios with various users.

  1. Open the playground at http://localhost:9876/graphql.
  2. Click on the Authentication sidebar on the left.
  3. Fill in the JWT secret and the payload and hit the "Update Authorization Token" button.
{
"role": "admin"
}

Authentication Sidebar Authentication Sidebar

This will update the Authorization header with a JWT token. Let's create two users:

mutation {
u1: createUser(
data: {
email: "[email protected]"
firstName: "F1"
lastName: "L1"
profileImageUrl: "https://example.com/1.jpg"
}
) {
id
}
u2: createUser(
data: {
email: "[email protected]"
firstName: "F2"
lastName: "L2"
profileImageUrl: "https://example.com/2.jpg"
}
) {
id
}
}

Since we are running this as an admin, Exograph allows it. (Try updating the role field in the payload to user and see what happens.)

Now go back to the Authentication sidebar and update the payload:

{
"sub": 1,
"role": "user"
}

Now create a few todos for this user. Try to query all the todos (or completed/incomplete todos). You will see only the todos that you created.

Let's repeat the process for another user. Update the sub field in the payload to 2 and try the same operations, but buy items for a different breakfast to tell them apart. Maybe "Buy cereal", "Buy orange juice", and "Buy coffee"?

You can already see that the access control rules are working. Let's try to have one user update another user's todo. Still as user 2, try to update an todo created by user 1:

mutation updateTodo {
updateTodo(id: 1, data: { title: "Don't buy", completed: true }) {
id
title
completed
}
}

Since the access control rule for Todo won't allow this operation, you will get back an empty response.

Declarative Testing

The accompanying code also includes tests using Exograph to ensure that the access control rules work as expected. You can run the tests with exo test.

Play around with the playground to see how the access control rules work. You will see that no one can query or mutate users except the admin. Users can query or mutate only their todos (admins can do anything). And all we needed to express this was a few lines of declarative code.

Conclusion

In this blog, we've explored the ease with which Exograph allows refining our application's access control rules as it scales. By adjusting a few lines, we can seamlessly adapt to new demands. Experimenting with these changes within the playground gives you confidence in your rules. Notably, the rules are clear and simple expressions. This makes it easy to reason about them and to communicate them with others.

Share:

Hello Exograph!

· 9 min read
Ramnivas Laddad
Co-founder @ Exograph
Shadaj Laddad
Co-founder @ Exograph
Luke Taylor
Co-founder @ Exograph

We are thrilled to announce the launch of Exograph: a new approach to building GraphQL backends. Exograph introduces a declarative language that lets you focus on your domain model and access control rules – you can have a backend up and running in minutes. Powered by a Rust-based runtime, Exograph ensures quick startup, efficient query execution, and minimal memory consumption. Additionally, Exograph provides a range of tools to help you get started quickly and keep pace with your application's evolving needs. Currently in preview, Exograph is available for download on our website as well as from our GitHub repository.

GraphQL has gained popularity as an API building choice due to its ability to allow clients to specify the required data, avoiding both over-fetching and under-fetching. Nevertheless, developing a GraphQL backend can be challenging and time-consuming, demanding substantial expertise in backend development to ensure security and efficiency. This is where Exograph comes in: it simplifies the process of building GraphQL backends by enabling developers to define their domain model and access control rules, without much additional complexity. The result is a highly productive approach to backend development, allowing you to focus on the unique aspects of your application. With Exograph, you can build your backend during a lunch break!

Here's a taste of Exograph. Let's say you want to build a GraphQL API for a todo app. Here is how you get it done with Exograph:

exo new todo-app
cd todo-app
exo yolo

That's it! You now have a GraphQL API with a GraphQL Playground running on http://localhost:9876/graphql waiting for you to explore.

Of course, this is partly because the "exo new" command creates a todo app as the starter code! But if you examine the starter source file, you will see that we could have done the same from scratch in no time.

@postgres
module TodoDatabase {
@access(true)
type Todo {
@pk id: Int = autoIncrement()
title: String
completed: Boolean
}
}

Essentially, this code defines the domain model and access control rules. Exograph uses this information to generate a GraphQL API for efficiently querying and mutating your data. It handles the underlying execution of SQL queries and enforces the access control rules, relieving you of those concerns. However, Exograph goes beyond managing persistent data; it also provides the capability to extend the core functionality by adding new APIs and augmenting existing ones.

The Exograph Way

Let's briefly tour the core ideas in Exograph that make it an excellent choice for your next project's GraphQL backend. For a more comprehensive introduction, please see our documentation.

Easy Domain Modelling

Exograph provides a declarative language that lets you concisely express your backend's domain model. This language draws inspiration from TypeScript and GraphQL IDL, so if you are already familiar with these or a similar language, you will feel right at home. Additionally, the language ensures type safety, providing compile-time errors instead of runtime surprises when there are errors in the code. Since you write Exograph code in just text files, you use Git (or any other version control system) as you would with any other code making it easy to collaborate with your team and track changes.

Let's say you want to build an e-commerce backend where a department may have multiple products. You define the domain model as follows:

@postgres
module EcommerceDatabase {
@access(true)
type Product {
@pk id: Int = autoIncrement()
name: String
description: String
price: Float
published: Boolean
department: Department
}

@access(true)
type Department {
@pk id: Int = autoIncrement()
name: String
products: Set<Product>?
}
}

Exograph VC Code Integration

Now you will get GraphQL APIs to query and mutate your data. For example, you can query all products in a department as follows:

query {
department(id: 1) {
name
products {
name
price
}
}
}

Fine-Grained Access Control

Access control is a fundamental feature of Exograph. When defining an Exograph model, you can specify access control rules alongside each element. This arrangement ensures that the rules are easy to comprehend and review. Moreover, Exograph's access control rules are highly flexible and customizable. They can be based on various factors such as user roles, the user-entity relationship, captcha verification, the time of day, or any other rules using the extensible "context" mechanism.

The earlier definition of e-commerce has a fatal flaw: anyone can query or mutate anything (due to @access(true)). Let's consider a scenario where you want to ensure that only administrators can add, update, or delete products or departments. Additionally, you want to restrict regular users to accessing only published products. You can express these requirements in Exograph as follows:

context AuthContext {
@jwt role: String
}

@postgres
module EcommerceDatabase {
@access(query=self.published || AuthContext.role=="admin", mutation=AuthContext.role=="admin")
type Product {
... same as before ...
}

@access(query=true, mutation=AuthContext.role=="admin")
type Department {
... same as before ...
}
}

Now non-admin users will get an authorization error if they try to create, update, or delete a product. Similarly, due to the self.published part of the rule, non-admin users will see only published products; it doesn't matter how they query it: directly or through a department.

Querying Through GraphiQL

Note the context element. Rather than baking specific forms of authentication into the language, Exograph provides a flexible mechanism to define your context. In this case, we define one that includes the user's role. You can then use this context in your access control rules. Combined with the extensibility offered by Deno modules, you can implement any authorization rules you need without waiting for Exograph to support it.

Extending the Core Functionality

One of the core principles of Exograph is to let developers mold the application to fit their needs. This way, developers can extend the platform rather than wait for us to implement specific functionalities.

Deno Modules

While working with data is often the dominant part of your application, it is not the whole story. You may also need to execute business logic and integrate your backend with other systems. The Deno modules support in Exograph makes this possible. You can write your business logic in TypeScript or JavaScript. In Exograph, you don't need to write separate backend (micro)services (although you can do that if you want). This embedding approach reduces the number of moving parts, simplifying the deployment process and making it easier to maintain your application.

Let's say you want to implement a GraphQL API to announce a product to possible buyers. You can implement it as follows:

@deno("announce.ts")
module ProductAnnouncement {
@access(AuthContext.role=="admin")
mutation announce(productId: Int, @inject exo: Exograph): String
}
const productQuery = `query getProduct($id: Int) { product(id: $id) { name, price } }`

export function announce(productId: number, exo: Exograph): string {
const product = exo.executeQuery(productQuery, {id: productId});
const potentialBuyers = exo.executeQuery(... analytics GraphQL query ...);
sendEmail(potentialBuyers, "New Product", `A new product ${product.name} is available`);
return "Announced";
}

Here, we declare the announce mutation. In its implementation, which may be in JavaScript or TypeScript, we use the Exo object to execute queries to get more information and send emails to potential buyers.

Interceptors

Exograph provides a mechanism to intercept main-line operations. The interceptor mechanism helps modularize cross-cutting concerns such as rate-limiting, auditing, logging, and performance monitoring. It also allows augmenting the core functionality. For instance, you can write an interceptor to email the user when they create an account. Additionally, it provides means to implement authorization rules that are not expressible in the declarative language.

Let's say, you want to audit all mutations by logging who executed what mutation and when. You can implement it as follows:

@deno("audit.ts")
module Audit {
@before("mutation *")
interceptor audit(operation: Operation, authContext: AuthContext)
}
export function audit(operation: Operation, authContext: AuthContext) {
log("Audit: ", operation.name(), authContext.id, new Date());
}

Over time, we will provide a library of interceptors for you to use out of the box. But you don't need to wait for that to happen or accept our way of implementing those concerns.

Fast and Efficient

Exograph starts fast (on a typical laptop, between 1-2 milliseconds and on AWS Lambda, around 200 milliseconds for a cold start), executes efficiently (often executing a single SQL query for even complex GraphQL queries or mutations), and uses very little memory (typically less than 64MB). These characteristics make it suitable for a wide range of applications: from small prototypes to large-scale applications and from traditional servers to serverless platforms.

Exograph's performance is partly due to the architecture that uses ahead-of-time build to reduce runtime computations and partly due to the awesomeness of the Rust programming language and the ecosystem around it.

Deployable Anywhere

You can deploy Exograph pretty much anywhere. Since the executable is a single binary, you can run it on your laptop, your data center, or a serverless platform. We also provide tools to create docker images, for example, to deploy to Fly.

Due to fast startup and low memory consumption, Exograph works well on serverless platforms. This helps to scale your application with varying demands. The exo cli supports creating and deploying your Exograph backend as an AWS Lambda function. We are working on similar tools for other serverless platforms.

Tooling for Every Stage of Development

Exograph includes a full suite of tools to support every stage of the development lifecycle: from initial development, to deployment, to maintenance. You can start with a simple "yolo" mode, where you don't need to worry about setting up the database. This mode is excellent for getting started quickly. Later, you can use the "dev" mode that automatically updates the server when you make changes. When working with databases, Exograph provides tools to create the initial schema, verify consistency between model and database, and run migrations as your code evolves.

Exograph provides a VS Code Extension to provide syntax highlighting, code completion, and error checking. It also includes a declarative way to test your GraphQL API.

This start-to-finish set of tools makes building and maintaining your GraphQL API easy.

Try Exograph Today

Exograph is currently in preview and available for download on our website. You can also find us on GitHub, Discord, and Twitter. This is just the beginning, with a lot more still to come. Please try it out and let us know how we can make it even better for you!

Share: