Skip to main content

Transactional Writes

In this guide you will learn how to update multiple relationship tuples in a single transaction.

When to use

Updating multiple relationship tuples is useful to keep system state consistent.

Before You Start

In order to understand this guide correctly you must be familiar with some OpenFGA Concepts and know how to develop the things that we will list below.

Assume that you have the following authorization model.
You have a type called tweet that can have a reader. You have another type called user that can have a follower and followed_by relationship.

model
schema 1.1

type tweet
relations
define viewer: [user, user:*, user#follower]

type user
relations
define follower: [user]
define followed_by: [user]

In addition, you will need to know the following:

Direct Access

You need to know how to create an authorization model and create a relationship tuple to grant a user access to an object. Learn more →

Modeling Public Access

You need to know how to grant public access to an object. Learn more →

OpenFGA Concepts

  • A Type: a class of objects that have similar characteristics
  • A User: an entity in the system that can be related to an object
  • A Relation: is a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system
  • An Object: represents an entity in the system. Users' relationships to it can be define through relationship tuples and the authorization model
  • A Relationship Tuple: a grouping consisting of a user, a relation and an object stored in OpenFGA

Step By Step

01. Adding And Removing Relationship Tuples In The Same Transaction

When you need to add or delete tuples in your store, you can do so by calling the Write API. For example, if you want to make tweet:1 public by making everyone a viewer, you write one tuple:

Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});

await fgaClient.write({
writes: [
{"user":"user:*","relation":"viewer","object":"tweet:1"}
],
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

And if you want to convert this tweet to private, you would need to delete that tuple:

Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});

await fgaClient.write({
deletes: [
{ user: 'user:*', relation: 'viewer', object: 'tweet:1'}
],
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

By removing the tuple, we made the tweet visible to no-one, which may not be what we want.

The Write API allows you to send up to 10 unique tuples in the request. (This limit applies to the sum of both writes and deletes in that request). This means we can submit one API call that converts the tweet from public to visible to only the user's followers.

Initialize the SDK
// ApiTokenIssuer, ApiAudience, ClientId and ClientSecret are optional.
// import the SDK
const { OpenFgaClient } = require('@openfga/sdk');

// Initialize the SDK with no auth - see "How to setup SDK client" for more options
const fgaClient = new OpenFgaClient({
apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example
storeId: process.env.FGA_STORE_ID,
authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request
});

await fgaClient.write({
writes: [
// Anne's followers can view tweet:1
{"_description":"Anne's followers can view tweet:1","user":"user:anne#follower","relation":"viewer","object":"tweet:1"}
],
deletes: [
// tweet:1 is no longer viewable by everyone (*)
{ user: 'user:*', relation: 'viewer', object: 'tweet:1'}
],
}, {
authorization_model_id: "1uHxCSuTP0VKPYSnkq1pbb1jeZw"
});

Having the ability to send multiple tuples per request is also useful when you want to maintain consistency. For example, if anne starts following becky, we want to be able to save the following two tuples, or neither of them:

[// Anne is a follower of Becky
{
"_description": "Anne is a follower of Becky",
"user": "user:anne",
"relation": "follower",
"object": "user:becky"
}// Becky is followed by Anne
{
"_description": "Becky is followed by Anne",
"user": "user:becky",
"relation": "followed_by",
"object": "user:anne"
}]
info

We have a type called user in this case because users can be related to each other, so the users now are a type in the system

The OpenFGA service will attempt to perform all the changes sent in a single Write API call in one transaction. If it can't (for example, if any of the requested changes fails), it will reject all of the changes.

Update relationship tuples in SDK

Learn about how to update relationship tuples in SDK.

OpenFGA API

Details on the write API in the OpenFGA reference guide.