User
This page documents the user
model.
Data Shape
Gadget's database stores user records by storing and retrieving each of the fields defined on the model in the Gadget Editor to a managed database. Gadget has generated a GraphQL type matching the configured fields for user:
1export type User = {23 __typename: 'User';45 /** The globally unique, unchanging identifier for this record. Assigned and managed by Gadget. */6 id: string;78 /** The time at which this record was first created. Set once upon record creation and never changed. Managed by Gadget. */9 createdAt: Date;1011 /** The time at which this record was last changed. Set each time the record is successfully acted upon by an action. Managed by Gadget. */12 updatedAt: Date;1314 /** The current state this record is in. Changed by invoking actions. Managed by Gadget. */15 state: (string | { [key: string]: Scalars['RecordState'] });1617 email: string;1819 roles: Role[];2021 sessions: SessionConnection;2223 tasks: TaskConnection;2425 /** Get all the fields for this record. Useful for not having to list out all the fields you want to retrieve, but slower. */26 _all: Record<string, any>;27};
1type User {2 """3 The globally unique, unchanging identifier for this record. Assigned and managed by Gadget.4 """5 id: GadgetID!67 """8 The time at which this record was first created. Set once upon record creation and never changed. Managed by Gadget.9 """10 createdAt: DateTime!1112 """13 The time at which this record was last changed. Set each time the record is successfully acted upon by an action. Managed by Gadget.14 """15 updatedAt: DateTime!1617 """18 The current state this record is in. Changed by invoking actions. Managed by Gadget.19 """20 state: RecordState!21 email: String22 roles: [Role!]23 sessions(24 """25 Returns the items in the list that come after the specified cursor.26 """27 after: String2829 """30 Returns the first n items from the list.31 """32 first: Int3334 """35 Returns the items in the list that come before the specified cursor.36 """37 before: String3839 """40 Returns the last n items from the list.41 """42 last: Int4344 """45 A list of sort orders to return the results in46 """47 sort: [SessionSort!]4849 """50 A list of filters to refine the results by51 """52 filter: [SessionFilter!]5354 """55 A free form text search query to find records matching56 """57 search: String58 ): SessionConnection!59 tasks(60 """61 Returns the items in the list that come after the specified cursor.62 """63 after: String6465 """66 Returns the first n items from the list.67 """68 first: Int6970 """71 Returns the items in the list that come before the specified cursor.72 """73 before: String7475 """76 Returns the last n items from the list.77 """78 last: Int7980 """81 A list of sort orders to return the results in82 """83 sort: [TaskSort!]8485 """86 A list of filters to refine the results by87 """88 filter: [TaskFilter!]8990 """91 A free form text search query to find records matching92 """93 search: String94 ): TaskConnection!9596 """97 Get all the fields for this record. Useful for not having to list out all the fields you want to retrieve, but slower.98 """99 _all: JSONObject!100}
You can preview what a real record's shape looks like by fetching it using the example-app API playground.
Any fetched user record will have this same User
type, and expose the same data by default, regardless of if it's fetched by ID or as part of a findMany
. This means you can select any of the record's fields wherever you like in a GraphQL query according to the use case at hand.
Retrieving one user record
Individual user records can be retrieved using the "find by ID" API endpoint. You can also return only some fields, or
extra fields beyond what Gadget retrieves by default, using the select
option.
The findOne
function throws an error if no matching record is found, which you will need to catch and handle. Alternatively, you can use the maybeFindOne
function, which returns null
if no record is found, without throwing an error.
Similarly, the useFindOne
React hook returns (but does not throw) an error when no matching record is found, while the useMaybeFindOne
hook simply returns null
if no record is found, without also returning an error.
const userRecord = await api.user.findOne("123");// => a stringconsole.log(userRecord.id);// => a Date objectconsole.log(userRecord.createdAt);
1const FindOneComponent = () => {2 const [{data, error, fetching}, refresh] = useFindOne(api.user, "123");34// => a string5console.log(data?.id);6// => a Date object7console.log(data?.createdAt);89return <>{JSON.stringify(data)}</>;10};
1query GetOneUser($id: GadgetID!) {2 user(id: $id) {3 __typename4 id5 state67 # ...89 createdAt10 email11 roles {12 key13 name14 }15 updatedAt16 }17}
{ "id": "123" }
const userRecord = await api.user.findOne("123");// => a stringconsole.log(userRecord.id);// => a Date objectconsole.log(userRecord.createdAt);
1const FindOneComponent = () => {2 const [{data, error, fetching}, refresh] = useFindOne(api.user, "123");34// => a string5console.log(data?.id);6// => a Date object7console.log(data?.createdAt);89return <>{JSON.stringify(data)}</>;10};
Retrieving the first of many user records
The first record from a list of records can be retrieved using the "find first" API endpoint. The source list of records can be filtered using the filter
option, sorted using the sort
option, searched using the search
option, though no pagination options are available on this endpoint. You can also return only some fields, or extra fields beyond what Gadget retrieves by default using the select
option.
The findFirst
function throws an error if no matching record is found, which you will need to catch and handle. Alternatively, you can use the maybeFindFirst
function, which returns null
if no record is found, without throwing an error.
Similarly, the useFindFirst
React hook returns (but does not throw) an error when no matching record is found, while the useMaybeFindFirst
hook simply returns null
if no record is found, without also returning an error.
const userRecord = await api.user.findFirst();console.log(userRecord.id); //=> a stringconsole.log(userRecord.createdAt); //=> a Date object
1const FindFirstComponent = (props) => {2 const [{data, error, fetching}, refresh] = useFindFirst(api.user);34// => a string5console.log(data?.id)6// => a Date object7console.log(data?.createdAt)89return <>{JSON.stringify(data)}</>;10};
1query FindManyUsers(2 $first: Int3 $search: String4 $sort: [UserSort!]5 $filter: [UserFilter!]6) {7 users(first: $first, search: $search, sort: $sort, filter: $filter) {8 edges {9 node {10 __typename11 id12 state1314 # ...1516 createdAt17 email18 roles {19 key20 name21 }22 updatedAt23 }24 }25 }26}
{ "first": 1 }
const userRecord = await api.user.findFirst();console.log(userRecord.id); //=> a stringconsole.log(userRecord.createdAt); //=> a Date object
1const FindFirstComponent = (props) => {2 const [{data, error, fetching}, refresh] = useFindFirst(api.user);34// => a string5console.log(data?.id)6// => a Date object7console.log(data?.createdAt)89return <>{JSON.stringify(data)}</>;10};
Retrieving many user records
Pages of user records can be retrieved by using the "find many" API endpoint. The returned records can be filtered using the filter
option, sorted using the sort
option, searched using the search
option, and paginated using standard Relay-style pagination options. You can also return only some fields, or extra fields beyond what Gadget retrieves by default using the select
option.
This GraphQL endpoint returns records in the Relay Connection style (as a list of edge
s with node
s and cursor
s) so they can be paginated. The users
GraphQL endpoint works with any Relay-compatible caching client, or you can use Gadget's JS client for pagination with the findMany
function.
Find a page of users
You can fetch one page of records with the user.findMany
JS method or the users
GraphQL field. No options are required, and records can be sorted with the sort
option and filtered with the filter
optional. The records returned will be implicitly sorted by ID ascending, unless you pass a sort
option.
const userRecords = await api.user.findMany();// => a numberconsole.log(userRecords.length);// => a stringconsole.log(userRecords[0].id);
1const FindManyComponent = () => {2 const [{data, error, fetching}, refresh] = useFindMany(api.user);3if (!data) return null;45// => a number6console.log(data.length)78return <>9{data.map(record => JSON.stringify(record))}1011 </>12};
1query FindManyUsers(2 $after: String3 $before: String4 $first: Int5 $last: Int6 $search: String7 $sort: [UserSort!]8 $filter: [UserFilter!]9) {10 users(11 after: $after12 before: $before13 first: $first14 last: $last15 search: $search16 sort: $sort17 filter: $filter18 ) {19 edges {20 node {21 __typename22 id23 state2425 # ...2627 createdAt28 updatedAt29 }30 }31 }32}
{}
const userRecords = await api.user.findMany();// => a numberconsole.log(userRecords.length);// => a stringconsole.log(userRecords[0].id);
1const FindManyComponent = () => {2 const [{data, error, fetching}, refresh] = useFindMany(api.user);3if (!data) return null;45// => a number6console.log(data.length)78return <>9{data.map(record => JSON.stringify(record))}1011 </>12};
Retrieving related model data
Your app's API is built using GraphQL, which makes it easy to retrieve data from related records through fields like belongs to and has many.
To optimize the performance of your API, data from related records is not retrieved by default, but you can change what data is retrieved with the `select` option to API calls.
For example, you can fetch has many data from the records linked by the sessions
field using the select
option:
1const userRecord = await api.user.findFirst({2 select: {3 id: true,4 sessions: {5 edges: {6 node: {7 id: true,8 createdAt: true,9 },10 },11 },12 },13});14console.log(userRecord.sessions?.map((edge) => edge.node.id)); //=> the related records' ids
1const FindRelatedComponent = (props) => {2const [{data, error, fetching}, refresh] = useFindFirst(api.user, {3 select: {4 id: true,5 sessions: {6 edges: {7 node: {8 id: true,9 createdAt: true10 }11 }12 }13 }14 });15 if (!data) return null;16 // show the related records ids17 return <>18 {data.sessions?.map(edge => edge.node.id)}19 </>20};
1const userRecord = await api.user.findFirst({2 select: {3 id: true,4 sessions: {5 edges: {6 node: {7 id: true,8 createdAt: true,9 },10 },11 },12 },13});14console.log(userRecord.sessions?.map((edge) => edge.node.id)); //=> the related records' ids
1const FindRelatedComponent = (props) => {2const [{data, error, fetching}, refresh] = useFindFirst(api.user, {3 select: {4 id: true,5 sessions: {6 edges: {7 node: {8 id: true,9 createdAt: true10 }11 }12 }13 }14 });15 if (!data) return null;16 // show the related records ids17 return <>18 {data.sessions?.map(edge => edge.node.id)}19 </>20};
When using an explicit select
option, be sure to include any fields you need from all models in your query, including the parent model. The default selection no longer applies when you pass select
.
Retrieving a single user record by a uniquely identifiable field
After adding a unique validation to a field, you can retrieve a single record by using the finders generated below. If you would like to edit the fields returned or filtering, see the filtering section.
Retrieving a single user record by id
Individual user records can be retrieved using the "find many" API endpoint pre-filtered by the field's id. Throws if stored data is not unique.
const userRecord = await api.user.findById("some-value");// => a stringconsole.log(userRecord.id);
1const FindByComponent = () => {2 const [{data, error, fetching}, refresh] = useFindBy(api.user.findById, "some-value");3 // => a string4 console.log(data?.id);56return <>{JSON.stringify(data)}</>;7}
const userRecord = await api.user.findById("some-value");// => a stringconsole.log(userRecord.id);
1const FindByComponent = () => {2 const [{data, error, fetching}, refresh] = useFindBy(api.user.findById, "some-value");3 // => a string4 console.log(data?.id);56return <>{JSON.stringify(data)}</>;7}
Retrieving a single user record by email
Individual user records can be retrieved using the "find many" API endpoint pre-filtered by the field's email. Throws if stored data is not unique.
const userRecord = await api.user.findByEmail("some-value");// => a stringconsole.log(userRecord.id);
1const FindByComponent = () => {2 const [{data, error, fetching}, refresh] = useFindBy(api.user.findByEmail, "some-value");3 // => a string4 console.log(data?.id);56return <>{JSON.stringify(data)}</>;7}
const userRecord = await api.user.findByEmail("some-value");// => a stringconsole.log(userRecord.id);
1const FindByComponent = () => {2 const [{data, error, fetching}, refresh] = useFindBy(api.user.findByEmail, "some-value");3 // => a string4 console.log(data?.id);56return <>{JSON.stringify(data)}</>;7}
Sorting
Records can be sorted in the database to retrieve them in a certain order. Records are always implicitly sorted by ID ascending unless an explicit sort on the id
field is defined. The GraphQL type UserSort
defines which fields can be sorted by.
Records can be sorted by multiple different fields and in multiple different directions by passing a list of UserSort
instead of just one.
Pass the sort
option to the JS client, or the sort
variable to a GraphQL query to sort the records returned.
const userRecords = await api.user.findMany({ sort: { createdAt: "Descending" } });
const [result, refresh] = useFindMany(api.user, { sort: { createdAt: "Descending" } });const { data, error, fetching } = result;
1query FindManyUsers($sort: [UserSort!]) {2 users(sort: $sort) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
{ "sort": { "createdAt": "Descending" } }
const userRecords = await api.user.findMany({ sort: { createdAt: "Descending" } });
const [result, refresh] = useFindMany(api.user, { sort: { createdAt: "Descending" } });const { data, error, fetching } = result;
Sort by multiple fields by passing an array of { [field]: "Ascending" | "Descending" }
objects.
const userRecords = await api.user.findMany({sort: [{ id: "Descending" }, { createdAt: "Ascending" }],});
1const [result, refresh] = useFindMany(api.user, {2 sort: [3 { id: "Descending" },4 { createdAt: "Ascending" }5 ]6});7const { data, error, fetching } = result;
1query FindManyUsers($sort: [UserSort!]) {2 users(sort: $sort) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
{ "sort": [{ "id": "Descending" }, { "createdAt": "Ascending" }] }
const userRecords = await api.user.findMany({sort: [{ id: "Descending" }, { createdAt: "Ascending" }],});
1const [result, refresh] = useFindMany(api.user, {2 sort: [3 { id: "Descending" },4 { createdAt: "Ascending" }5 ]6});7const { data, error, fetching } = result;
Available fields for sorting
You can sort user
records by the following fields:
Field | Sort type |
---|---|
id | Ascending / Descending |
email | Ascending / Descending |
state | Ascending / Descending |
createdAt | Ascending / Descending |
updatedAt | Ascending / Descending |
For example, you can sort by the id
field:
const userRecords = await api.user.findMany({sort: { id: "Descending" },});
const [result, refresh] = useFindMany(api.user, {sort: { id: 'Descending'}})const { data, error, fetching } = result;
1query FindManyUsers($sort: [UserSort!]) {2 users(sort: $sort) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
{ "sort": { "id": "Descending" } }
const userRecords = await api.user.findMany({sort: { id: "Descending" },});
const [result, refresh] = useFindMany(api.user, {sort: { id: 'Descending'}})const { data, error, fetching } = result;
Read more about sorting in the Sorting and Filtering reference.
Searching
user records can be searched using Gadget's built in full text search functionality. Gadget search is appropriate
for powering autocompletes, searchable tables, or other experiences where humans are writing search queries. It's typo tolerant, synonym aware
and supports simple search operators like !
to exclude search terms.
Search Users by passing the search
parameter with a search query string.
Search isn't field specific in Gadget -- all String or RichText field types are searched with the built in search functionality.
const userRecords = await api.user.findMany({search: "a specific phrase to search for",});
const [result, refresh] = useFindMany(api.user, {search: "a specific phrase to search for",});const { data, error, fetching } = result;
1query FindManyUsers($search: String) {2 users(search: $search) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
{ "search": "a specific phrase to search for" }
const userRecords = await api.user.findMany({search: "a specific phrase to search for",});
Filtering
user records can be filtered to return only the appropriate records. Records can be filtered on any field, including those managed by Gadget or fields added by developers. Filters can be combined with sorts, searches and paginated using cursor-based Relay pagination.
Filter Users by passing the filter
parameter with a filter object. Filter objects are nestable boolean conditions expressed as JS objects capturing a key, an operator, and usually a value.
const yesterday = new Date(Date.now() - 86400000);const userRecords = await api.user.findMany({filter: { createdAt: { greaterThan: yesterday } },});
const yesterday = new Date(Date.now() - 86400000);const [result, refresh] = useFindMany(api.user, {filter: { createdAt: { greaterThan: yesterday }}})const { data, error, fetching } = result;
1query FindManyUsers($filter: [UserFilter!]) {2 users(filter: $filter) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
{ "filter": { "createdAt": { "greaterThan": "2024-11-20T09:49:59.163Z" } } }
const yesterday = new Date(Date.now() - 86400000);const userRecords = await api.user.findMany({filter: { createdAt: { greaterThan: yesterday } },});
const yesterday = new Date(Date.now() - 86400000);const [result, refresh] = useFindMany(api.user, {filter: { createdAt: { greaterThan: yesterday }}})const { data, error, fetching } = result;
Available fields for filtering
The GraphQL type UserFilter
defines which fields can be filtered on.
Field | Filter type |
---|---|
id | IDFilter |
email | EmailFilter |
state | RecordStateFilter |
roles | RoleAssignmentsFilter |
createdAt | DateTimeFilter |
updatedAt | DateTimeFilter |
For example, we can filter user
records on the id field:
const userRecords = await api.user.findMany({filter: {id: { isSet: true },},});
1const [result, refresh] = useFindMany(api.user, {2 filter: {3 id: {isSet: true}4 }5})6const { data, error, fetching } = result;
1query FindManyUsers($filter: [UserFilter!]) {2 users(filter: $filter) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
{ "filter": { "id": { "isSet": true } } }
const userRecords = await api.user.findMany({filter: {id: { isSet: true },},});
1const [result, refresh] = useFindMany(api.user, {2 filter: {3 id: {isSet: true}4 }5})6const { data, error, fetching } = result;
Read more about the available filters in the Sorting and Filtering reference.
Combining filters
Records can be filtered by multiple different fields simultaneously. If you want to combine filters using boolean logic, nest them under the AND
, OR
, or NOT
keys of a parent filter. Filters can be nested deeply by passing multiple levels boolean condition filters.
You can also pass a list of filters to the filter
parameter which will be implicitly AND
ed with one another such that they all need to match for a record to be returned.
1const yesterday = new Date(Date.now() - 86400000);2const oneWeekAgo = new Date(Date.now() - 604800000);3const userRecords = await api.user.findMany({4 filter: {5 OR: [6 {7 createdAt: { greaterThan: oneWeekAgo },8 },9 {10 updatedAt: { greaterThan: yesterday },11 },12 ],13 },14});
1const yesterday = new Date(Date.now() - 86400000);2const oneWeekAgo = new Date(Date.now() - 604800000);3const [result, refresh] = useFindMany(api.user, {4 filter: {5 OR: [6 {7 createdAt: { greaterThan: oneWeekAgo },8 },9 {10 updatedAt: { greaterThan: yesterday },11 },12 ]13 }14})15const { data, error, fetching } = result;
1query FindManyUsers($filter: [UserFilter!]) {2 users(filter: $filter) {3 edges {4 node {5 __typename6 id7 state89 # ...1011 createdAt12 updatedAt13 }14 }15 }16}
1{2 "filter": {3 "OR": [4 { "createdAt": { "greaterThan": "2024-11-14T09:49:59.163Z" } },5 { "updatedAt": { "greaterThan": "2024-11-20T09:49:59.163Z" } }6 ]7 }8}
1const yesterday = new Date(Date.now() - 86400000);2const oneWeekAgo = new Date(Date.now() - 604800000);3const userRecords = await api.user.findMany({4 filter: {5 OR: [6 {7 createdAt: { greaterThan: oneWeekAgo },8 },9 {10 updatedAt: { greaterThan: yesterday },11 },12 ],13 },14});
1const yesterday = new Date(Date.now() - 86400000);2const oneWeekAgo = new Date(Date.now() - 604800000);3const [result, refresh] = useFindMany(api.user, {4 filter: {5 OR: [6 {7 createdAt: { greaterThan: oneWeekAgo },8 },9 {10 updatedAt: { greaterThan: yesterday },11 },12 ]13 }14})15const { data, error, fetching } = result;
Pagination
All Gadget record lists, including the top level user finder as well as associations to user, are structured as GraphQL connections. GraphQL connections are the de facto standard for querying lists and support cursor-based forward and backward pagination. When querying via GraphQL, you must select the edges
field and then the node
field to get the user record. When querying using a Gadget API client, the GraphQL queries are generated for you and the records are unwrapped and returned as a GadgetRecordList
ready for use.
user pagination supports the standard GraphQL connection pagination arguments: first
+ after
, or last
+ before
.
Pagination is done using cursors, which you can retrieve from the edge.cursor
field or the pageInfo.startCursor
properties.
const userRecords = await api.user.findMany({ first: 25 });// => no greater than 25console.log(userRecords.length);
1const FirstPage = () => {2 const [{ data, error, fetching }, refresh] = useFindMany(api.user, {first: 25})3 if (!data) return null;45// => no greater than 256console.log(data.length);78return <>9{data.map(record => JSON.stringify(record))}1011 </>12}
1query FindManyUsers($first: Int, $after: String) {2 users(first: $first, after: $after) {3 pageInfo {4 hasNextPage5 hasPreviousPage6 startCursor7 endCursor8 }9 edges {10 cursor11 node {12 __typename13 id14 state1516 # ...1718 createdAt19 updatedAt20 }21 }22 }23}
{ "first": 25 }
const userRecords = await api.user.findMany({ first: 25 });// => no greater than 25console.log(userRecords.length);
1const FirstPage = () => {2 const [{ data, error, fetching }, refresh] = useFindMany(api.user, {first: 25})3 if (!data) return null;45// => no greater than 256console.log(data.length);78return <>9{data.map(record => JSON.stringify(record))}1011 </>12}
const userRecords = await api.user.findMany({ after: "example-cursor", first: 25 });
1const SecondPage = (props) => {2 const [{ data, error, fetching }, refresh] = useFindMany(api.user, {after: "example-cursor", first: 25});3 if (!data) return null;45// => no greater than 256console.log(data.length);78return <>9{data.map(record => JSON.stringify(record))}1011 </>12}
1query FindManyUsers($first: Int, $after: String) {2 users(first: $first, after: $after) {3 pageInfo {4 hasNextPage5 hasPreviousPage6 startCursor7 endCursor8 }9 edges {10 cursor11 node {12 __typename13 id14 state1516 # ...1718 createdAt19 updatedAt20 }21 }22 }23}
{ "first": 25, "after": "abcdefg" }
const userRecords = await api.user.findMany({ after: "example-cursor", first: 25 });
1const SecondPage = (props) => {2 const [{ data, error, fetching }, refresh] = useFindMany(api.user, {after: "example-cursor", first: 25});3 if (!data) return null;45// => no greater than 256console.log(data.length);78return <>9{data.map(record => JSON.stringify(record))}1011 </>12}
Pagination Limits
Root-level record finders like users
support a maximum page size of 250 records and a default page size of 50 records. The page size is controlled using the first
or last
GraphQL field arguments.
Related record finders that access lists of records through a has many or has many through field support a maximum page size of 100 records and a default page size of 50 records.
Get the next or previous page
When using the generated JavaScript API client, including the api
parameter in a Gadget code effect, the record lists returned from findMany calls can be paginated using the nextPage()
or previousPage()
option.
Both nextPage()
and previousPage()
will throw an error if the corresponding hasNextPage
or hasPreviousPage
is false
.
1const userRecords = await api.user.findMany();2if (userRecords.hasNextPage) {3 const nextPage = await userRecords.nextPage();4}5if (userRecords.hasPreviousPage) {6 const prevPage = await userRecords.previousPage();7}
1const userRecords = await api.user.findMany();2if (userRecords.hasNextPage) {3 const nextPage = await userRecords.nextPage();4}5if (userRecords.hasPreviousPage) {6 const prevPage = await userRecords.previousPage();7}
When using React and paging through records, you can use cursors to get the previous or next pages of records. This is an example of a React component that pages forward and backward through 2 records at a time for user.
1// your Gadget project's API Client2import { api } from "../api";3import { useFindMany } from "@gadgetinc/react";4import { useCallback, useState } from "react";56export default function TestComponent() {7 // the number of records per page8 const NUM_ON_PAGE = 2;910 const [cursor, setCursor] = useState({ first: NUM_ON_PAGE });11 // using Gadget React hooks to fetch records of user12 const [{ data, fetching, error }] = useFindMany(api.user, {13 ...cursor,14 });1516 const getNextPage = useCallback(() => {17 // use first + after to page forwards18 setCursor({ first: NUM_ON_PAGE, after: data.endCursor });19 }, [data]);2021 const getPreviousPage = useCallback(() => {22 // use last + before to page backwards23 setCursor({ last: NUM_ON_PAGE, before: data.startCursor });24 }, [data]);2526 return (27 <div>28 <button onClick={getPreviousPage} disabled={!data?.hasPreviousPage}>29 Previous page30 </button>31 <button onClick={getNextPage} disabled={!data?.hasNextPage}>32 Next page33 </button>34 {!fetching && data.map((d) => <div>{d.id}</div>)}35 </div>36 );37}
1// your Gadget project's API Client2import { api } from "../api";3import { useFindMany } from "@gadgetinc/react";4import { useCallback, useState } from "react";56export default function TestComponent() {7 // the number of records per page8 const NUM_ON_PAGE = 2;910 const [cursor, setCursor] = useState({ first: NUM_ON_PAGE });11 // using Gadget React hooks to fetch records of user12 const [{ data, fetching, error }] = useFindMany(api.user, {13 ...cursor,14 });1516 const getNextPage = useCallback(() => {17 // use first + after to page forwards18 setCursor({ first: NUM_ON_PAGE, after: data.endCursor });19 }, [data]);2021 const getPreviousPage = useCallback(() => {22 // use last + before to page backwards23 setCursor({ last: NUM_ON_PAGE, before: data.startCursor });24 }, [data]);2526 return (27 <div>28 <button onClick={getPreviousPage} disabled={!data?.hasPreviousPage}>29 Previous page30 </button>31 <button onClick={getNextPage} disabled={!data?.hasNextPage}>32 Next page33 </button>34 {!fetching && data.map((d) => <div>{d.id}</div>)}35 </div>36 );37}
Get all records
If you need to get all available data for user, you will need to paginate through all pages of data. If you have a large amount of data, this can take a long time. Make sure you need to collect all data at once before writing a pagination loop that reads all records! If you are querying records for display in a UI and cannot display all your records at once, we don't recommend fetching all the data beforehand - instead, use the cursor to read additional data when the user needs it.
If you need all data for analytics applications or to collect some statistics on your data, consider options like intermediate models and pre-defined data rollups.
If you have determined that you need all your data, you can fetch it using cursors and a loop. We also suggest using select
so that you only grab fields that are needed, in addition to applying a filter
, if possible. Using first
with the maximum allowable value will also allow you to grab the maximum number of records you can at once.
1// use allRecords to store all records2const allRecords = [];3let records = await api.user.findMany({4 first: 250,5 select: {6 id: true,7 },8 filter: {9 // add filter conditions, if possible10 },11});1213allRecords.push(...records);1415// loop through additional pages to get all protected orders16while (records.hasNextPage) {17 // paginate18 records = await records.nextPage();19 allRecords.push(...records);20}
1// use allRecords to store all records2const allRecords = [];3let records = await api.user.findMany({4 first: 250,5 select: {6 id: true,7 },8 filter: {9 // add filter conditions, if possible10 },11});1213allRecords.push(...records);1415// loop through additional pages to get all protected orders16while (records.hasNextPage) {17 // paginate18 records = await records.nextPage();19 allRecords.push(...records);20}
Selecting fields, and fields of fields
When using the JavaScript client, all of findOne
, maybeFindOne
, findMany
, findFirst
, maybeFindFirst
, and various action functions, allow requesting specific fields of a user and its relationships. The select
option controls which fields are selected in the generated GraphQL query sent to the Gadget API. Pass each field you want to select in an object, with true
as the value for scalar fields, and a nested object of the same shape for nested fields.
Gadget has a default selection that will retrieve all of the scalar fields for a user. If you don't pass a select
option to a record finder, this default selection will be used.
1// fetch only the id and createdAt field2const userRecords = await api.user.findMany({3 select: { id: true, createdAt: true },4});5// fetch all the scalar fields for the model, but no relationship fields6const userRecords = await api.user.findMany();
const SelectionComponent = (props) => {// fetch only the id and createdAt fieldconst [{data, error, fetching}, refresh] = useFindMany(api.user, {select: { id: true, createdAt: true }});// ...}
1// fetch only the id and createdAt field2const userRecords = await api.user.findMany({3 select: { id: true, createdAt: true },4});5// fetch all the scalar fields for the model, but no relationship fields6const userRecords = await api.user.findMany();
const SelectionComponent = (props) => {// fetch only the id and createdAt fieldconst [{data, error, fetching}, refresh] = useFindMany(api.user, {select: { id: true, createdAt: true }});// ...}
If you want to include the ID of a belongs to or has one relationship in your response, you can add the ID of the relationship field to the select
parameter. For example, say we have a ticket
model that belongs to a flight
model. We can get the flight that the ticket is associated with like so:
const [{ data, fetching, error }, _refetch] = useFindMany(api.ticket, {select: { id: true, ticketNumber: true, flightId: true },});
const [{ data, fetching, error }, _refetch] = useFindMany(api.ticket, {select: { id: true, ticketNumber: true, flightId: true },});
Where flight
is the name of the relationship field on the ticket
model. You must append Id
in camel case. If flightId
is not included in the select statement, it will not be part of the returned object.
Realtime subscriptions with live
queries
Each read function in the user runs once and returns a static set of results. There is also a second mode for subscribing to the results of the query as they change over time.
By passing live: true
to any read query or hook, you'll get a stream of results as data changes in the backend, instead of just one result. This is useful for automatically updating your UI when any record is created, updated, or deleted in your backend database.
Realtime queries are issued by passing live: true
to your API call or React hook. For example, you can query and stream changes to the record with id 123
:
// calling findOne with live: true will return a stream of results as an AsyncIterator, not just one resultfor await (const record of api.user.findOne("123", { live: true })) {// record will be a GadgetRecord objectconsole.log("new record version", record);}
1export const Record123 = (props) => {2 const [{data, error, fetching}] = useFindOne(api.user,3 "123", // the id of the record to fetch4 {5 // make this a realtime live query6 live: true,7 });8 if (!data) return null;9 return (10 <div>11 Record 123 last updated at {data.updatedAt}12 </div>13 );14}
// calling findOne with live: true will return a stream of results as an AsyncIterator, not just one resultfor await (const record of api.user.findOne("123", { live: true })) {// record will be a GadgetRecord objectconsole.log("new record version", record);}
1export const Record123 = (props) => {2 const [{data, error, fetching}] = useFindOne(api.user,3 "123", // the id of the record to fetch4 {5 // make this a realtime live query6 live: true,7 });8 if (!data) return null;9 return (10 <div>11 Record 123 last updated at {data.updatedAt}12 </div>13 );14}
You can also subscribe to realtime results for pages of records. For example, we can fetch and render a list of the first ten user records, which will automatically update when backend data changes:
// calling findMany with live: true will return a stream of results as an AsyncIterator, not just one resultfor await (const list of api.user.findMany({ live: true, first: 10 })) {// list will be an array of GadgetRecord objectsconsole.log("new query result", list);}
1export const FirstPage = (props) => {2 const [{data, error, fetching}] = useFindMany(api.user, {3 // make this a realtime live query4 live: true,5 first: 106 });7 if (!data) return null;8 return (9 <div>10 {data.map((record) => (11 <div key={record.id}>{record.id}</div>12 ))}13 </div>14 );15}
// calling findMany with live: true will return a stream of results as an AsyncIterator, not just one resultfor await (const list of api.user.findMany({ live: true, first: 10 })) {// list will be an array of GadgetRecord objectsconsole.log("new query result", list);}
1export const FirstPage = (props) => {2 const [{data, error, fetching}] = useFindMany(api.user, {3 // make this a realtime live query4 live: true,5 first: 106 });7 if (!data) return null;8 return (9 <div>10 {data.map((record) => (11 <div key={record.id}>{record.id}</div>12 ))}13 </div>14 );15}
For more information, see the Realtime Queries guide.
Type Safety
The select
option is fully type-safe if you're using TypeScript. The returned GadgetRecord
type will have a <Shape>
exactly matching the fields and nested fields you selected.
This behavior of selecting only some fields is built right into GraphQL. If you want to limit or expand what you retrieve from a GraphQL query, include or exclude those fields in your GraphQL query. For more information on executing GraphQL queries, see GraphQL.
1// fetch the id and createdAt field, and fetch some nested fields from an example relationship field named `someRelatedObject`2const userRecords = await api.user.findMany({3 select: {4 id: true,5 createdAt: true,6 someRelatedObject: { id: true, createdAt: true },7 },8});
const SelectionComponent = (props) => {// fetch the id and createdAt field, and fetch some nested fields from an example relationship field named `someRelatedObject`const [{data, error, fetching}, refresh] = useFindMany(api.user, {select: { id: true, createdAt: true, someRelatedObject: { id: true, createdAt: true } }});// ...}
1// fetch the id and createdAt field, and fetch some nested fields from an example relationship field named `someRelatedObject`2const userRecords = await api.user.findMany({3 select: {4 id: true,5 createdAt: true,6 someRelatedObject: { id: true, createdAt: true },7 },8});
const SelectionComponent = (props) => {// fetch the id and createdAt field, and fetch some nested fields from an example relationship field named `someRelatedObject`const [{data, error, fetching}, refresh] = useFindMany(api.user, {select: { id: true, createdAt: true, someRelatedObject: { id: true, createdAt: true } }});// ...}
Combining parameters
Sort, search, filtering, selection, and pagination parameters can be combined to access the exact set of records needed for your use case.
1const userRecords = await api.user.findMany({2 search: "<some search query>",3 sort: { createdAt: "Descending" },4 filter: { updatedAt: { greaterThan: new Date(Date.now() - 86400000) } },5 select: { id: true, createdAt: true },6 first: 25,7 after: "abcdefg",8});
1const [result, refresh] = useFindMany(api.user, {2 search: "<some search query>",3 sort: { createdAt: "Descending" },4 filter: { updatedAt: { greaterThan: new Date(Date.now() - 86400000)}},5 select: { id: true, createdAt: true },6 first: 25,7 after: "abcdefg"8});9const { data, error, fetching } = result;
1query FindManyUsers(2 $after: String3 $before: String4 $first: Int5 $last: Int6 $search: String7 $sort: [UserSort!]8 $filter: [UserFilter!]9) {10 users(11 after: $after12 before: $before13 first: $first14 last: $last15 search: $search16 sort: $sort17 filter: $filter18 ) {19 edges {20 node {21 __typename22 id23 state2425 # ...2627 createdAt28 email29 roles {30 key31 name32 }33 updatedAt34 }35 }36 }37}
1{2 "search": "<some search query>",3 "sort": { "createdAt": "Descending" },4 "filter": { "updatedAt": { "greaterThan": "2024-11-20T09:49:59.268Z" } },5 "first": 25,6 "after": "abcdefg"7}
1const userRecords = await api.user.findMany({2 search: "<some search query>",3 sort: { createdAt: "Descending" },4 filter: { updatedAt: { greaterThan: new Date(Date.now() - 86400000) } },5 select: { id: true, createdAt: true },6 first: 25,7 after: "abcdefg",8});
1const [result, refresh] = useFindMany(api.user, {2 search: "<some search query>",3 sort: { createdAt: "Descending" },4 filter: { updatedAt: { greaterThan: new Date(Date.now() - 86400000)}},5 select: { id: true, createdAt: true },6 first: 25,7 after: "abcdefg"8});9const { data, error, fetching } = result;
Invoking Actions
user records are changed by invoking Actions. Actions are the things that "do" stuff -- update records, make API calls, call backend code, etc. Actions with a GraphQL API trigger each have one corresponding GraphQL mutation and a corresponding function available in the API client libraries. Nested Actions can also be invoked with the API client, by providing the actions as input to any relationship fields.
Action Result format
Each API action returns results in the same format that includes a success indicator, errors, and the actual result if the action succeeded. The result is the record that was acted on for a model action, or a list of records for a bulk action, or a JSON blob for Global Actions. Model actions that delete the record don't return the record.
The success
field returns a boolean indicating if the action executed as expected. Any execution errors are returned in the errors
object, which will always be null
if success
is true or contain ExecutionError
objects if success
is false.
ExecutionError
objects always have a message
describing what error prevented the action from succeeding, as well as a code
attribute that gives a stable, searchable, human-readable error class code for referencing this specific error. Details on each error code can be found in the Errors documentation. All ExecutionError
object types returned by the GraphQL object can be one of many types of error, where some types have extra data that is useful for remedying the error. All error types will always have message
and code
properties, but some, like InvalidRecordError
have extra fields for use by clients.
Errors when using the generated client
The generated JavaScript client automatically interprets errors from invoking actions and throws JavaScript Error
instances if the action didn't succeed. The Error
objects it throws are rich, and expose extra error properties beyond just message
and code
if they exist.
Errors thrown by the JavaScript client are easiest to catch by using a try/catch
statement around an await
, like so:
1import {2 GadgetOperationError,3 InvalidRecordError,4} from "@gadgetinc/api-client-core";56// must be in an async function to use await` syntax7export async function run({ api }) {8 try {9 return await api.exampleModel.create({ name: "example record name" });10 } catch (error) {11 if (error instanceof GadgetOperationError) {12 // a recognized general error has occurred, retry the operation or inspect \error.code\`13 console.error(error);14 } else if (error instanceof InvalidRecordError) {15 // the submitted input data for the action was invalid, inspect the invalid fields which \`InvalidRecordError\` exposes16 console.error(error.validationErrors);17 } else {18 // an unrecognized error occurred like an HTTP connection interrupted error or a syntax error. Re-throw it because it's not clear what to do to fix it19 throw error;20 }21 }22}
1import {2 GadgetOperationError,3 InvalidRecordError,4} from "@gadgetinc/api-client-core";56// must be in an async function to use await` syntax7export async function run({ api }) {8 try {9 return await api.exampleModel.create({ name: "example record name" });10 } catch (error) {11 if (error instanceof GadgetOperationError) {12 // a recognized general error has occurred, retry the operation or inspect \error.code\`13 console.error(error);14 } else if (error instanceof InvalidRecordError) {15 // the submitted input data for the action was invalid, inspect the invalid fields which \`InvalidRecordError\` exposes16 console.error(error.validationErrors);17 } else {18 // an unrecognized error occurred like an HTTP connection interrupted error or a syntax error. Re-throw it because it's not clear what to do to fix it19 throw error;20 }21 }22}
For more information on error codes, consult the Errors documentation.
user signUp
Input
signUp
accepts the following input parameters:
1export type SignUpUserInput = {23 email?: (Scalars['String'] | null) | null;45 password?: (Scalars['String'] | null) | null;67 sessions?: (SessionHasManyInput | null)[];89 tasks?: (TaskHasManyInput | null)[];10};11121314export type SignUpUserArguments = {1516 user?: SignUpUserInput | null;17};
1input SignUpUserInput {2 email: String3 password: String4 sessions: [SessionHasManyInput]5 tasks: [TaskHasManyInput]6}78input SignUpUserArguments {9 user: SignUpUserInput10}
const userRecord = await api.user.signUp({email: "[email protected]",password: "nohacking123%",roles: ["signed-in"],});
1const ExampleRunSignUpComponent = (props) => {2 const [{ data, error, fetching }, signUp] = useAction(api.user.signUp);34 return (5 <>6 <button7 onClick={async () => {8 await signUp({9 email: "[email protected]",10 password: "nohacking123%",11 roles: ["signed-in"],12 });13 }}14 >15 Run Action16 </button>17 Result: {JSON.stringify(data)}18 </>19 );20};
1mutation SignUpUser($user: SignUpUserInput) {2 signUpUser(user: $user) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 user {18 __typename19 id20 state21 createdAt22 email23 roles {24 key25 name26 }27 updatedAt28 }29 }30}
1{2 "user": {3 "email": "[email protected]",4 "password": "nohacking123%",5 "roles": ["signed-in"]6 }7}
const userRecord = await api.user.signUp({email: "[email protected]",password: "nohacking123%",roles: ["signed-in"],});
1const ExampleRunSignUpComponent = (props) => {2 const [{ data, error, fetching }, signUp] = useAction(api.user.signUp);34 return (5 <>6 <button7 onClick={async () => {8 await signUp({9 email: "[email protected]",10 password: "nohacking123%",11 roles: ["signed-in"],12 });13 }}14 >15 Run Action16 </button>17 Result: {JSON.stringify(data)}18 </>19 );20};
Output
signUp
returns the user. In the JS client, the fields returned can be controlled with the select
option. In GraphQL, the return format is the action result format, which includes the record if the action was successful. You can include or exclude the fields you need right in the mutation itself.
1type SignUpUserResult {2 success: Boolean!3 errors: [ExecutionError!]4 actionRun: String5 user: User6}
user update
Input
update
operates on one user in particular, identified by the id
variable.update
accepts the following input parameters:
1export type UpdateUserInput = {23 email?: (Scalars['String'] | null) | null;45 password?: (Scalars['String'] | null) | null;67 sessions?: (SessionHasManyInput | null)[];89 tasks?: (TaskHasManyInput | null)[];10};11121314export type UpdateUserArguments = {1516 user?: UpdateUserInput | null;17};
1input UpdateUserInput {2 email: String3 password: String4 sessions: [SessionHasManyInput]5 tasks: [TaskHasManyInput]6}78input UpdateUserArguments {9 user: UpdateUserInput10}
const userRecord = await api.user.update("123", {email: "[email protected]",password: "nohacking123%",roles: ["signed-in"],});
1const ExampleRunUpdateComponent = (props) => {2 const [{ data, error, fetching }, update] = useAction(api.user.update);34 return (5 <>6 <button7 onClick={async () => {8 await update({9 email: "[email protected]",10 id: "123",11 password: "nohacking123%",12 roles: ["signed-in"],13 });14 }}15 >16 Run Action17 </button>18 Result: {JSON.stringify(data)}19 </>20 );21};
1mutation UpdateUser($user: UpdateUserInput, $id: GadgetID!) {2 updateUser(user: $user, id: $id) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 user {18 __typename19 id20 state21 createdAt22 email23 roles {24 key25 name26 }27 updatedAt28 }29 }30}
1{2 "user": {3 "email": "[email protected]",4 "password": "nohacking123%",5 "roles": ["signed-in"]6 },7 "id": "123"8}
const userRecord = await api.user.update("123", {email: "[email protected]",password: "nohacking123%",roles: ["signed-in"],});
1const ExampleRunUpdateComponent = (props) => {2 const [{ data, error, fetching }, update] = useAction(api.user.update);34 return (5 <>6 <button7 onClick={async () => {8 await update({9 email: "[email protected]",10 id: "123",11 password: "nohacking123%",12 roles: ["signed-in"],13 });14 }}15 >16 Run Action17 </button>18 Result: {JSON.stringify(data)}19 </>20 );21};
Output
update
returns the user. In the JS client, the fields returned can be controlled with the select
option. In GraphQL, the return format is the action result format, which includes the record if the action was successful. You can include or exclude the fields you need right in the mutation itself.
1type UpdateUserResult implements UpsertUserResult {2 success: Boolean!3 errors: [ExecutionError!]4 actionRun: String5 user: User6}
user delete
The delete
action destroys the record.
Input
delete
operates on one user in particular, identified by the id
variable.
await api.user.delete("123");
1const ExampleRunDeleteComponent = (props) => {2 const [{ data, error, fetching }, _delete] = useAction(api.user.delete);34 return (5 <>6 <button7 onClick={async () => {8 await _delete({9 id: "123",10 });11 }}12 >13 Run Action14 </button>15 Result: {JSON.stringify(data)}16 </>17 );18};
1mutation DeleteUser($id: GadgetID!) {2 deleteUser(id: $id) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 }18}
{ "id": "123" }
await api.user.delete("123");
1const ExampleRunDeleteComponent = (props) => {2 const [{ data, error, fetching }, _delete] = useAction(api.user.delete);34 return (5 <>6 <button7 onClick={async () => {8 await _delete({9 id: "123",10 });11 }}12 >13 Run Action14 </button>15 Result: {JSON.stringify(data)}16 </>17 );18};
Output
delete
deletes the record, so it returns void
in the JS client. In GraphQL it returns only the success
and errors
from the action result format.
type DeleteUserResult {success: Boolean!errors: [ExecutionError!]actionRun: String}
user create
Input
create
accepts the following input parameters:
1export type CreateUserInput = {23 email?: (Scalars['String'] | null) | null;45 password?: (Scalars['String'] | null) | null;67 sessions?: (SessionHasManyInput | null)[];89 tasks?: (TaskHasManyInput | null)[];10};11121314export type CreateUserArguments = {1516 user?: CreateUserInput | null;17};
1input CreateUserInput {2 email: String3 password: String4 sessions: [SessionHasManyInput]5 tasks: [TaskHasManyInput]6}78input CreateUserArguments {9 user: CreateUserInput10}
const userRecord = await api.user.create({email: "[email protected]",password: "nohacking123%",roles: ["signed-in"],});
1const ExampleRunCreateComponent = (props) => {2 const [{ data, error, fetching }, create] = useAction(api.user.create);34 return (5 <>6 <button7 onClick={async () => {8 await create({9 email: "[email protected]",10 password: "nohacking123%",11 roles: ["signed-in"],12 });13 }}14 >15 Run Action16 </button>17 Result: {JSON.stringify(data)}18 </>19 );20};
1mutation CreateUser($user: CreateUserInput) {2 createUser(user: $user) {3 success4 errors {5 message6 ... on InvalidRecordError {7 validationErrors {8 apiIdentifier9 message10 }11 record12 model {13 apiIdentifier14 }15 }16 }17 user {18 __typename19 id20 state21 createdAt22 email23 roles {24 key25 name26 }27 updatedAt28 }29 }30}
1{2 "user": {3 "email": "[email protected]",4 "password": "nohacking123%",5 "roles": ["signed-in"]6 }7}
const userRecord = await api.user.create({email: "[email protected]",password: "nohacking123%",roles: ["signed-in"],});
1const ExampleRunCreateComponent = (props) => {2 const [{ data, error, fetching }, create] = useAction(api.user.create);34 return (5 <>6 <button7 onClick={async () => {8 await create({9 email: "[email protected]",10 password: "nohacking123%",11 roles: ["signed-in"],12 });13 }}14 >15 Run Action16 </button>17 Result: {JSON.stringify(data)}18 </>19 );20};
Output
create
returns the user. In the JS client, the fields returned can be controlled with the select
option. In GraphQL, the return format is the action result format, which includes the record if the action was successful. You can include or exclude the fields you need right in the mutation itself.
1type CreateUserResult implements UpsertUserResult {2 success: Boolean!3 errors: [ExecutionError!]4 actionRun: String5 user: User6}
user upsert
Input
upsert
accepts the following input parameters:
1export type UpsertUserInput = {23 id?: (Scalars['GadgetID'] | null) | null;45 email?: (Scalars['String'] | null) | null;67 password?: (Scalars['String'] | null) | null;89 /** A string list of Gadget platform Role keys to assign to this record */10 roles?: ((Scalars['String'] | null))[];1112 sessions?: (SessionHasManyInput | null)[];1314 tasks?: (TaskHasManyInput | null)[];15};16171819export type UpsertUserArguments = {2021 /** An array of Strings */22 on?: ((Scalars['String'] | null))[];2324 user?: UpsertUserInput | null;25};
1input UpsertUserInput {2 id: GadgetID3 email: String4 password: String56 """7 A string list of Gadget platform Role keys to assign to this record8 """9 roles: [String!]10 sessions: [SessionHasManyInput]11 tasks: [TaskHasManyInput]12}1314input UpsertUserArguments {15 """16 An array of Strings17 """18 on: [String!]19 user: UpsertUserInput20}
1const result = await api.user.upsert({2 email: "[email protected]",3 id: "123",4 on: ["email"],5 password: "nohacking123%",6 roles: ["signed-in"],7});
1const ExampleRunUpsertComponent = (props) => {2 const [{ data, error, fetching }, upsert] = useAction(api.user.upsert);34 return (5 <>6 <button7 onClick={async () => {8 await upsert({9 email: "[email protected]",10 id: "123",11 on: ["email"],12 password: "nohacking123%",13 roles: ["signed-in"],14 });15 }}16 >17 Run Action18 </button>19 Result: {JSON.stringify(data)}20 </>21 );22};
1mutation UpsertUser($on: [String!], $user: UpsertUserInput) {2 upsertUser(on: $on, user: $user) {3 success4 errors {5 message6 }7 }8}
1{2 "on": ["email"],3 "user": {4 "email": "[email protected]",5 "id": "123",6 "password": "nohacking123%",7 "roles": ["signed-in"]8 }9}
1const result = await api.user.upsert({2 email: "[email protected]",3 id: "123",4 on: ["email"],5 password: "nohacking123%",6 roles: ["signed-in"],7});
1const ExampleRunUpsertComponent = (props) => {2 const [{ data, error, fetching }, upsert] = useAction(api.user.upsert);34 return (5 <>6 <button7 onClick={async () => {8 await upsert({9 email: "[email protected]",10 id: "123",11 on: ["email"],12 password: "nohacking123%",13 roles: ["signed-in"],14 });15 }}16 >17 Run Action18 </button>19 Result: {JSON.stringify(data)}20 </>21 );22};
Output
upsert
returns the user. In the JS client, the fields returned can be controlled with the select
option. In GraphQL, the return format is the action result format, which includes the record if the action was successful. You can include or exclude the fields you need right in the mutation itself.
interface UpsertUserResult {success: Boolean!errors: [ExecutionError!]actionRun: String}
Linking related records
When running actions on the user
model, you can set the value of any belongs to or has many relationships in the same API call.
Linking existing child records
For has many fields, actions on user
records can also update child records to link to this record. You can link existing child records by passing a nested action to invoke on the child record. Gadget will implicitly link the referenced child record to the outer parent record in the API call.
Linking to a new child record
You can also nest actions on child records to create new children when running actions on the parent. Pass a nestedcreate
action for the child record, and Gadget will implicitly link it to the outer parent record in your API call.
Bulk Actions
You can run the same action for an array of inputs all at once with bulk actions. Bulk Actions are executed as a single API call and offer better performance when running the same action on many records.
Creates, updates, deletes, and custom actions can all be run in bulk. Bulk Actions repeat the same action each time across a variety of different records. If you want to call different actions, you can't use Bulk Actions and must instead make multiple API calls.
If a bulk action group fails on some of the individual records, the remaining records will still be processed, and the list of errors will be returned in the result. Only the records which succeed in executing the action will be returned in the result.
Bulk user signUp
Input
bulkSignUpUsers
operates on a set of users, identified by the ids
variable.
1const userRecords = await api.user.bulkSignUp([2 {3 email: "[email protected]",4 password: "nohacking123%",5 },6 {7 email: "[email protected]",8 password: "nohacking123%",9 },10]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkSignUp] = useBulkAction(api.user.bulkSignUp);34 return (5 <>6 <button7 onClick={async () => {8 await bulkSignUp([9 {10 email: "[email protected]",11 password: "nohacking123%",12 },13 {14 email: "[email protected]",15 password: "nohacking123%",16 },17 ]);18 console.log(data?.length); //=> a number19 console.log(data?.[0].id); //=> a string20 }}21 >22 Run Action23 </button>24 Result: {JSON.stringify(data)}25 </>26 );27};
1mutation BulkSignUpUsers($inputs: [BulkSignUpUsersInput!]!) {2 bulkSignUpUsers(inputs: $inputs) {3 success4 errors {5 message6 }7 users {8 id9 state10 createdAt11 email12 roles {13 key14 name15 }16 updatedAt17 }18 }19}
1{2 "inputs": [3 { "user": { "email": "[email protected]", "password": "nohacking123%" } },4 { "user": { "email": "[email protected]", "password": "nohacking123%" } }5 ]6}
1const userRecords = await api.user.bulkSignUp([2 {3 email: "[email protected]",4 password: "nohacking123%",5 },6 {7 email: "[email protected]",8 password: "nohacking123%",9 },10]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkSignUp] = useBulkAction(api.user.bulkSignUp);34 return (5 <>6 <button7 onClick={async () => {8 await bulkSignUp([9 {10 email: "[email protected]",11 password: "nohacking123%",12 },13 {14 email: "[email protected]",15 password: "nohacking123%",16 },17 ]);18 console.log(data?.length); //=> a number19 console.log(data?.[0].id); //=> a string20 }}21 >22 Run Action23 </button>24 Result: {JSON.stringify(data)}25 </>26 );27};
Output
bulkSignUpUsers
returns the set of users that successfully completed the action. In the JS client, the fields returned for the record can be controlled with the select
option. In GraphQL, the response is in the action result format, which includes a selection that completed the action successfully.The success
property will be false
if any of the records failed to perform their action and the returned set of users
will only include the ones that completed successfully.
1"""2The output when running the signUp on the user model in bulk.3"""4type BulkSignUpUsersResult {5 """6 Boolean describing if all the bulk actions succeeded or not7 """8 success: Boolean!910 """11 Aggregated list of errors that any bulk action encountered while processing12 """13 errors: [ExecutionError!]1415 """16 The list of all changed user records by each sent bulk action. Returned in the same order as the input bulk action params.17 """18 users: [User]19}
Bulk user update
Input
bulkUpdateUsers
operates on a set of users, identified by the ids
variable.
1const userRecords = await api.user.bulkUpdate([2 {3 email: "[email protected]",4 id: "123",5 password: "nohacking123%",6 },7 {8 email: "[email protected]",9 id: "456",10 password: "nohacking123%",11 },12]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkUpdate] = useBulkAction(api.user.bulkUpdate);34 return (5 <>6 <button7 onClick={async () => {8 await bulkUpdate([9 {10 email: "[email protected]",11 id: "123",12 password: "nohacking123%",13 },14 {15 email: "[email protected]",16 id: "456",17 password: "nohacking123%",18 },19 ]);20 console.log(data?.length); //=> a number21 console.log(data?.[0].id); //=> a string22 }}23 >24 Run Action25 </button>26 Result: {JSON.stringify(data)}27 </>28 );29};
1mutation BulkUpdateUsers($inputs: [BulkUpdateUsersInput!]!) {2 bulkUpdateUsers(inputs: $inputs) {3 success4 errors {5 message6 }7 users {8 id9 state10 createdAt11 email12 roles {13 key14 name15 }16 updatedAt17 }18 }19}
1{2 "inputs": [3 {4 "user": { "email": "[email protected]", "password": "nohacking123%" },5 "id": "123"6 },7 {8 "user": { "email": "[email protected]", "password": "nohacking123%" },9 "id": "456"10 }11 ]12}
1const userRecords = await api.user.bulkUpdate([2 {3 email: "[email protected]",4 id: "123",5 password: "nohacking123%",6 },7 {8 email: "[email protected]",9 id: "456",10 password: "nohacking123%",11 },12]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkUpdate] = useBulkAction(api.user.bulkUpdate);34 return (5 <>6 <button7 onClick={async () => {8 await bulkUpdate([9 {10 email: "[email protected]",11 id: "123",12 password: "nohacking123%",13 },14 {15 email: "[email protected]",16 id: "456",17 password: "nohacking123%",18 },19 ]);20 console.log(data?.length); //=> a number21 console.log(data?.[0].id); //=> a string22 }}23 >24 Run Action25 </button>26 Result: {JSON.stringify(data)}27 </>28 );29};
Output
bulkUpdateUsers
returns the set of users that successfully completed the action. In the JS client, the fields returned for the record can be controlled with the select
option. In GraphQL, the response is in the action result format, which includes a selection that completed the action successfully.The success
property will be false
if any of the records failed to perform their action and the returned set of users
will only include the ones that completed successfully.
1"""2The output when running the update on the user model in bulk.3"""4type BulkUpdateUsersResult {5 """6 Boolean describing if all the bulk actions succeeded or not7 """8 success: Boolean!910 """11 Aggregated list of errors that any bulk action encountered while processing12 """13 errors: [ExecutionError!]1415 """16 The list of all changed user records by each sent bulk action. Returned in the same order as the input bulk action params.17 """18 users: [User]19}
Bulk user delete
bulkDeleteUsers
action destroys the records.
Input
bulkDeleteUsers
operates on a set of users, identified by the ids
variable.
await api.user.bulkDelete(["123", "456"]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkDelete] = useBulkAction(api.user.bulkDelete);34 return (5 <>6 <button7 onClick={async () => {8 await bulkDelete({9 ids: ["123", "456"],10 });11 }}12 >13 Run Action14 </button>15 Result: {JSON.stringify(data)}16 </>17 );18};
1mutation BulkDeleteUsers($ids: [GadgetID!]!) {2 bulkDeleteUsers(ids: $ids) {3 success4 errors {5 message6 }7 }8}
{ "ids": ["123", "456"] }
await api.user.bulkDelete(["123", "456"]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkDelete] = useBulkAction(api.user.bulkDelete);34 return (5 <>6 <button7 onClick={async () => {8 await bulkDelete({9 ids: ["123", "456"],10 });11 }}12 >13 Run Action14 </button>15 Result: {JSON.stringify(data)}16 </>17 );18};
Output
bulkDeleteUsers
deletes the record, so it returns void
in the JS client. In GraphQL it returns only the success
and errors
from the action result format.
1"""2The output when running the delete on the user model in bulk.3"""4type BulkDeleteUsersResult {5 """6 Boolean describing if all the bulk actions succeeded or not7 """8 success: Boolean!910 """11 Aggregated list of errors that any bulk action encountered while processing12 """13 errors: [ExecutionError!]14}
Bulk user create
Input
bulkCreateUsers
operates on a set of users, identified by the ids
variable.
1const userRecords = await api.user.bulkCreate([2 {3 email: "[email protected]",4 password: "nohacking123%",5 },6 {7 email: "[email protected]",8 password: "nohacking123%",9 },10]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkCreate] = useBulkAction(api.user.bulkCreate);34 return (5 <>6 <button7 onClick={async () => {8 await bulkCreate([9 {10 email: "[email protected]",11 password: "nohacking123%",12 },13 {14 email: "[email protected]",15 password: "nohacking123%",16 },17 ]);18 console.log(data?.length); //=> a number19 console.log(data?.[0].id); //=> a string20 }}21 >22 Run Action23 </button>24 Result: {JSON.stringify(data)}25 </>26 );27};
1mutation BulkCreateUsers($inputs: [BulkCreateUsersInput!]!) {2 bulkCreateUsers(inputs: $inputs) {3 success4 errors {5 message6 }7 users {8 id9 state10 createdAt11 email12 roles {13 key14 name15 }16 updatedAt17 }18 }19}
1{2 "inputs": [3 { "user": { "email": "[email protected]", "password": "nohacking123%" } },4 { "user": { "email": "[email protected]", "password": "nohacking123%" } }5 ]6}
1const userRecords = await api.user.bulkCreate([2 {3 email: "[email protected]",4 password: "nohacking123%",5 },6 {7 email: "[email protected]",8 password: "nohacking123%",9 },10]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkCreate] = useBulkAction(api.user.bulkCreate);34 return (5 <>6 <button7 onClick={async () => {8 await bulkCreate([9 {10 email: "[email protected]",11 password: "nohacking123%",12 },13 {14 email: "[email protected]",15 password: "nohacking123%",16 },17 ]);18 console.log(data?.length); //=> a number19 console.log(data?.[0].id); //=> a string20 }}21 >22 Run Action23 </button>24 Result: {JSON.stringify(data)}25 </>26 );27};
Output
bulkCreateUsers
returns the set of users that successfully completed the action. In the JS client, the fields returned for the record can be controlled with the select
option. In GraphQL, the response is in the action result format, which includes a selection that completed the action successfully.The success
property will be false
if any of the records failed to perform their action and the returned set of users
will only include the ones that completed successfully.
1"""2The output when running the create on the user model in bulk.3"""4type BulkCreateUsersResult {5 """6 Boolean describing if all the bulk actions succeeded or not7 """8 success: Boolean!910 """11 Aggregated list of errors that any bulk action encountered while processing12 """13 errors: [ExecutionError!]1415 """16 The list of all changed user records by each sent bulk action. Returned in the same order as the input bulk action params.17 """18 users: [User]19}
Bulk user upsert
Input
bulkUpsertUsers
operates on a set of users, identified by the ids
variable.
1const results = await api.user.bulkUpsert([2 {3 email: "[email protected]",4 id: "123",5 on: ["email"],6 },7 {8 email: "[email protected]",9 id: "123",10 on: ["email"],11 },12]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkUpsert] = useBulkAction(api.user.bulkUpsert);34 return (5 <>6 <button7 onClick={async () => {8 await bulkUpsert([9 {10 email: "[email protected]",11 id: "123",12 on: ["email"],13 },14 {15 email: "[email protected]",16 id: "123",17 on: ["email"],18 },19 ]);20 console.log(data?.length); //=> a number21 console.log(data?.[0].id); //=> a string22 }}23 >24 Run Action25 </button>26 Result: {JSON.stringify(data)}27 </>28 );29};
1mutation BulkUpsertUsers($inputs: [BulkUpsertUsersInput!]!) {2 bulkUpsertUsers(inputs: $inputs) {3 success4 errors {5 message6 }7 results8 }9}
1{2 "inputs": [3 { "on": ["email"], "user": { "email": "[email protected]", "id": "123" } },4 { "on": ["email"], "user": { "email": "[email protected]", "id": "123" } }5 ]6}
1const results = await api.user.bulkUpsert([2 {3 email: "[email protected]",4 id: "123",5 on: ["email"],6 },7 {8 email: "[email protected]",9 id: "123",10 on: ["email"],11 },12]);
1const RunActionComponent = (props) => {2 const [{ data, error, fetching }, bulkUpsert] = useBulkAction(api.user.bulkUpsert);34 return (5 <>6 <button7 onClick={async () => {8 await bulkUpsert([9 {10 email: "[email protected]",11 id: "123",12 on: ["email"],13 },14 {15 email: "[email protected]",16 id: "123",17 on: ["email"],18 },19 ]);20 console.log(data?.length); //=> a number21 console.log(data?.[0].id); //=> a string22 }}23 >24 Run Action25 </button>26 Result: {JSON.stringify(data)}27 </>28 );29};
Output
bulkUpsertUsers
returns the set of users that successfully completed the action. In the JS client, the fields returned for the record can be controlled with the select
option. In GraphQL, the response is in the action result format, which includes a selection that completed the action successfully.The success
property will be false
if any of the records failed to perform their action and the returned set of users
will only include the ones that completed successfully.
1"""2The result of a bulk upsert operation for the user model3"""4type BulkUpsertUsersResult {5 """6 Boolean describing if all the bulk actions succeeded or not7 """8 success: Boolean!910 """11 Aggregated list of errors that any bulk action encountered while processing12 """13 errors: [ExecutionError!]1415 """16 The results of each upsert action in the bulk operation17 """18 users: [User]19}