Exploring a TypeScript Wrapper for FaunaDB FQLv10 API – Seeking Feedback and Guidance

Hey Fauna team,

I’m currently in the process of building a TypeScript (TS) wrapper for Fauna’s FQLv10 API, and I wanted to reach out to get some feedback and guidance from the community and possibly the Fauna engineering team.

Background:

Over a year ago, I started using FaunaDB for a future SaaS project. I’ve been building out some backend microservices as part of this long-term project, and FaunaDB became the database of choice due to its powerful features. As part of the journey, I recently began migrating from FQL v4 to FQL v10, and during that migration, I realized the need for a custom wrapper to simplify interaction with the API, especially from a TypeScript perspective.

I’ve invested around 20 hours so far into this side project, which has allowed me to explore Fauna’s API, improve my FQL syntax, and build something functional that aligns with my larger SaaS goals.

What I’m working on:

I’m aiming to create a flexible TypeScript wrapper that helps abstract FQLv10 into easier-to-use constructs. For instance, I’d like to offer helper methods that handle common operations like .getByName() or .getWhere() directly within the wrapper, without requiring manual FQL construction each time. This would enable me (and others) to work with Fauna in a more seamless and TS-friendly way.

Here’s a breakdown of what I’m doing:

  1. Generating FQL instructions: Building functions that output FQLv10 fragments or full instructions as strings.
  2. Collection Abstractions: Creating a way to define collections and operations like CRUD in TypeScript while keeping it flexible for additional use cases.
  3. Manual Testing: I’ve been testing the instructions in the Fauna web app and writing unit tests along the way.
  4. Class-based Structure: Eventually, I’ll organize this into classes for easier use and extensibility.
  5. Community Focus: I believe this could be useful for others in the community as well, once it’s more polished.

Use Case Example:

One interesting idea I’ve been working on is defining client-specific schemas and collections. My goal is to lazily create backend artifacts like collections as needed, per client, making it easy to segregate data.

For instance, I’m working on something like this:

class Notifications extends Collection {
  constructor(config: ...) {
    // Constructor logic here
  }

  // Define methods like getByName, getWhere, etc.
}

This abstraction would allow me to use TypeScript methods instead of manually constructing FQL queries. Here’s a snippet of the type of code I’m working through:

export function createRole(c: RoleConfig) {
  c = checkRoleConfig(c);
  return `
    if (Role.byName("${c.name}") != null) {
      "${c.name} role already exists";
    } else {
      Role.create({
        name: "${c.name}"
        ${renderPriviledges(c)}
        ${renderMembership(c)}
        ${renderData(c)}
      });
    }
  `;
}

My Concerns and Questions:

As an early adopter of FQLv10 and someone who’s still learning the nuances, I’m curious about a few things:

  1. Fauna’s Roadmap for FQLv10 TS Support: Is Fauna planning to release an official FQLv10 TypeScript wrapper soon? If so, I don’t want to spend too much time duplicating efforts.
  2. General Feedback: Does this project sound useful or interesting to the wider Fauna community? Would this type of wrapper be something worth open-sourcing, and are there potential opportunities for collaboration with the Fauna team?
  3. Best Practices: Are there any specific technical considerations or best practices you would recommend for this project? Especially as I’m new to certain aspects of FQL syntax and Fauna’s advanced features.
  4. Schema Validation: Internally, I’ve been using Zod for schema validation in TS, which has been quite useful for ensuring config object correctness before generating FQL instructions. Is this a good approach, or is there a more Fauna-native way to achieve similar results?

Any feedback, advice, or guidance would be greatly appreciated. I’d love to hear your thoughts on whether this project could be valuable to the community and if collaboration with the Fauna team would make sense.

Thanks for your time and consideration!

1 Like

Hi @AndrewRedican! First of all, thank you very much for the detailed write-up. Really appreciate your effort and participation in the community!

Let me try to answer your questions most directly, then continue discussion

  1. Is Fauna planning to release an official FQLv10 TypeScript wrapper soon? We are not working on an ORM typescript wrapper to replace using FQL queries directly. That said, it is a common ask so we have been watching the community and listening for how folks are building their applications. An official ORM is not out of the question, but we are focused on other improvements right now, including better Language Server Protocol (LSP), VS Code plugin, and CLI tooling for project management.
  2. Does this project sound useful or interesting to the wider Fauna community? More feedback on what works for folks and what doesn’t is greatly appreciated. We would also love to hear more about this from others in the community.
  3. Are there any specific technical considerations or best practices you would recommend for this project? Definitely stuff to discuss here!
  4. Schema Validation… We’re currently in beta for new schema-enforcement features including Field Definitions. Typechecking based on your schema can ensure the correctness of the persisted data, though working with tools like Zod are still great for confirming that the types received over the internet are still what you expect.

Fauna Schema Language (FSL)

One thing that stands out to me is that you don’t mention Fauna Schema Language (FSL), so if we can take a bit of a detour, I’d like to explore with you what, if any, attempts to use or considerations you have made to manage your schema using FSL.

As far as best practices go, defining your database schema in FSL and committing the FSL to source control is what we recommend. Even in a multi-tenant SaaS project, you can reuse FSL to bootstrap child databases.

Since you are coming directly from having a FQL v4 framework, it sounds like you have a lot of bootstrapping code in place to upsert schema elements (e.g. “If role exists return, else create it”). Continuing with FQL v10 to manage schema programmatically is a valid strategy, but I strongly encourage you to evaluate if FSL is appropriate for you first. And we would appreciate feedback on how folks are considering using FSL to handle some elements that must be dynamic, for example if your SaaS customers each need some amount of configuration or customization.

Here are the latest docs for project management. https://docs.fauna.com/fauna/current/build/cli/#proj-config

We’re actively working to improve how to setup projects and maintain FSL in code, so the CLI and for sure project management should be seeing some major updates in the near future.

Typescript framework

What you are doing to model your data and provide easier-to-use constructs is a great approach! As I said above, I would attempt to avoid managing schema through FQL, but providing classes and types for your specific domain model makes a lot of sense.

And I know you’re not the only one working on better typescript integration.

Typechecked queries return the result type

If building a framework that validates types, perhaps this is something to utilize. See the static_type field for successful response payloads. Here’s the type info in the JS driver: fauna-js/src/wire-protocol.ts at 505d39377e22ce685a85ffc75b10713619dc1be4 · fauna/fauna-js · GitHub

This is how the dashboard shell displays the type for your results in the lower-right corner. SOme contrived examples:

FQL composition

In this example you are not using the fql function provided. The fql tagged-template function works in a way to prevent query string injection. Using the fql function means that the request to fauna will be broken into raw FQL strings and encoded arguments that Fauna can type check. You can build raw strings yourself, but you should be aware of this.

For example, a request from JS like this:

vonst id = "1234"
const value = 42
fql`Thing.byId(${id})!.update({ value: ${value} })`

would be encoded like

{ "query": {
  "fql": [
    "Thing.byId(", 
    { value: "1234" }, 
    ")!.update({ value: ",
    { value: { "@int": "42" } },
    "})"
  ]
}

So Fauna won’t allow you to, for example, provide garbage like "1234")!.delete() // as an argument.

Tagging queries

Another best practice to consider is including tags for your queries. Query logs (plan) - Fauna Docs

If you are building a framework, then you might consider some convention for tagging queries from the classes that create them, and allowing tags to be passed through from the caller.

1 Like

Hi @ptpaterson,

Thank you for the fantastic response! There was a lot of valuable information to process, and I realized I need to clarify a few points from my original post.

  1. My use of Zod for validation has primarily been limited to validating configuration objects for FQL operations—such as what goes into a .create({...}). While I am currently using Zod to validate values before attempting a save, the specific use case here is a bit different. I’m aware of Zod’s schema capabilities and plan to leverage them in the future. I use Zod in my TypeScript project because it allows for both pre-save validation and generating TypeScript interfaces, which is a great combination. Additionally, I believe it’s more efficient to validate before sending the request in the first place, as waiting for Fauna’s schema error responses could introduce unnecessary delays. To summarize, in this side project, I’m mainly using Zod to validate the configuration.
  2. I really appreciate FSL’s declarative syntax and would prefer to use it, but I haven’t been able to figure out how to pass or execute FSL to Fauna in order to spin up databases for my clients. This is the primary reason I’ve been using FQL instead, as I can run it directly in the Fauna shell UI, creating a useful feedback loop for iteration. If I could understand how to use FSL to achieve the same—ideally by passing it to a Fauna endpoint to spin up a specific database schema (e.g., collections, etc.)—I would definitely prefer that.
  3. What I’m currently building is essentially a tool that constructs FQL queries for me by assembling the instructions. I fully intend to use fql, like this:
fql`the instruction string I’ve constructed with my lightweight TypeScript wrapper`

At the moment, my focus has been almost exclusively on constructing these instructions. You could think of it as a glorified StringBuilder API, designed to build a string of FQL instructions.