When using Data Connect, your app sometimes might need server-computed values because the values need to be computed at the time of data creation.
As an example, suppose you’re adding a user
field to AddReview
, and you want to set the field to the current user ID when a review is added. You could add a variable and change the app to set it from the client side, but an attacker may leverage that to post reviews attributed to another user. It’s a lot of work to ensure security.
With Data Connect, you only have to change the server side by using server values. This means the server dynamically populates fields in your tables using stored or readily-computable values using particular server-side expressions.
Data Connect already supported some server values, and now, there are even more server values that you can use!
Previously
Until now, the only supported server values were: auth.uid
, request.time
, and uuidV4()
. As an example, you could define a field with a timestamp applied when the field is accessed using the expression createdAt: Timestamp! @default(expr: "request.time")
.
This might be used when a user creates their account, so the schema could look like this:
type User @table(key: "userId") {
# other fields
userId: String! @default(expr: "auth.uid") # or (expr: “uuidV4()”)
createdAt: Timestamp! @default(expr: "request.time")
}
This schema not only means that the corresponding database table gets created but also that the server automatically fills in the correct values.
To use these in your queries or mutations, you use <field>_expr
, which are auto-generated on all fields. For example, to add a user:
mutation AddUser {
user_insert(data: {
userId_expr: "auth.uid"
createdAt_expr: "request.time"
})
}
New Updates
The supported server values above still work, but now, you can use CEL expressions to make more complex field values.
To do so, you use <field>_expr
, which are auto-generated on all fields. The schema stays the same as above, and to add a user:
mutation AddUser {
user_insert(data: {
userId_expr: “auth.token.email”
createdAt_expr: "request.time"
})
}
Without using _expr
, auth.token.email
and request.time
will be string literals (meaning userId
’s value will have the literal value “auth.token.email”. createdAt
will not hold the correct value, as the literal “request.time” string parses into a specific Timestamp and will be that value every single time).
Aside from the expressions that worked before today, you can now create even more complex fields! Let’s say you need to add a prefix to all userIDs located in a specific region. With server values, this is how you can do so:
mutation AddUserInUS {
user_insert(data: {
userId_expr: "'US' + auth.uid"
})
}
If the auth.uid
is abc123
, the userId for this particular user will be USabc123
.
Note: to ensure proper computation of the expression, you need to wrap the entire expression in double quotes and use single quotes inside the expression.
For a more interesting example, let’s say you’re building a medical app with doctors creating profiles and you’re using Firebase Auth to authenticate:
type Profile @table(key: "id") {
id: String! # cannot be UUID since Firebase Auth ids are not UUIDs
displayName: String!
}
You want to add a new user with their Firebase Auth UID as the ID:
mutation CreateProfile @auth(level: USER) {
profile_insert(data: {
id_expr: "auth.uid",
})
}
When a doctor fills out profile information, they provide their degree, like MD, DO, PA, NP, and MA. In displaying this information to clients, the app needs to list this degree after the name. Therefore in the app code, each new profile insertion also needs to include their name, concatenated with a comma and their degree. Adding to the code above, you can use a CEL expression value in displayName_expr
to do name concatenation:
mutation CreateProfile($name: String!, $degree: String!) @auth(level: PUBLIC) {
profile_insert(
data: {
id_expr: "auth.uid",
displayName_expr: "vars.name + ', ' + vars.degree"
}
)
}
vars
above is shorthand for request.variables
to make writing code easier and succinct. There is no advantage in request.variables
as it’s longer and these two get the exact same information.
Later, let’s say a user has saved the name of the doctor and degree they have, and the user wants to see the doctor’s profile. To query for results for the correct doctor:
query FindProfilesWithDegree($name: String, $suffix: String) @auth(level: PUBLIC) {
profiles(
where: { displayName: {eq_expr: "vars.name + ', ' + vars.suffix"} }) {
id
displayName
}
)
}
With <field>_expr
being able to evaluate CEL expressions, you have a lot more options for server values!
Try it out!
These new features and improvements are designed to unlock new use cases and make Data Connect a more capable database service for your apps. We’re excited to see what you build with it and these new features. Happy coding!