Blog
/
Announcement

Introducing views in Gadget: Performant data queries

Published
September 8, 2025
Last updated
September 9, 2025
Run complex serverside queries without compromising on app performance.
TLDR: Read, transform, and aggregate data much, much faster with views!

Developers can now offload complex read queries, aggregations, and joins to Gadget’s infrastructure to minimize load times and maximize performance.

Views are used for performing aggregations or transformations across multiple records within one or more models. They allow you to calculate metrics across large datasets, join data across multiple models, and simplify the interface for running these complex queries.

For example, you could power a dashboard and calculate the total number of students and teachers for a given city, and list the available courses:

api/views/educationMetrics.gelly

Without views, you would need to manually fetch, paginate, count, and aggregate records in your backend. Execution time could balloon as your number of records grows. Views pushes this work down to the database and returns results much faster than manual aggregation.

Out of the box, views include support for parameter inputs, result selection and aliasing, and pagination for when a query includes more than 10,000 returned records.

When processing large amounts of data, developers are often stuck relying on slow, resource-intensive read operations, or re-writing the same queries over and over again. With views, you don’t need to worry about managing database load or carefully optimizing each query for performance, because Gadget handles all of that for you.

A better way to query data

Views are read-only queries executed on a fleet of high-performance read replicas optimized for executing these queries. Your views are converted to performant SQL automatically generated by Gadget thanks to our deep insight into the shape of your data models. 

You don’t need to manually set up read replicas or worry about query routing — Gadget views handle all of this out of the box. And your big, expensive view executions won’t interrupt normal query processing for the rest of your application, which is a major time saver and performance win for developers.

Views can even be run in the API playground which makes for easy building, testing, and experimentation.

Getting started with views

Views are written in Gelly, Gadget’s data access language. Gelly is a superset to GraphQL, and provides a declarative way to write queries that are either computed or re-computed across records at the database level, while optimizing for efficiency across a high number of rows. 

Although it’s similar to SQL and GraphQL, it provides developers more flexibility by allowing for things like relationship traversals, reusable fragments, and more ergonomic expressions. It comes with some quality of life improvements over alternative languages, and eliminates some of the minor annoyances like requiring trailing commas in plain old SQL.

Views can be saved into a <inline-code>.gelly<inline-code> file or run with <inline-code>.view()<inline-code> in any namespace in your app’s API client (or GraphQL API).

When a view is saved in a <inline-code>.gelly<inline-code> file, that view is automatically added to your app’s API. A view saved in <inline-code>api/views/getStudentMetrics.gelly<inline-code> can be executed with <inline-code>await api.getStudentMetrics()<inline-code>, and <inline-code>api/models/shopifyProduct/views/getProductTotals.gelly<inline-code> is run with <inline-code>await api.shopifyProduct.getProductTotals();<inline-code>.

Running a named view from the API client

When building views in the API playground, you can use <inline-code>.view()<inline-code> to execute inline queries. The <inline-code>.view()<inline-code> function is available on all namespaces in your app. For example, to get some aggregate data on the number of comments for a blog, you could run:

Running an inlne view from the API client

Named vs inline views

We recommend writing your views in named <inline-code>.gelly<inline-code> files when possible. This enables you to easily call the view using your API client, gives you better insight into access control permissions for the query, and allows Gadget to lint your views for errors.

There are still good uses for running inline views using the <inline-code>.view()<inline-code> API:

  • You are building your view using the API playground. Instead of writing in a <inline-code>.gelly<inline-code> file and running the action in the playground to test, you can inline everything in the playground.
  • You are building a view dynamically, and change the shape of the view query based on external criteria. For example, a user might be able to add and select custom fields to be included in a view.

Run queries from your frontend and backend

Your views can be run in both your Gadget backend and frontend, but it is important to note that frontend use requires the user’s role to have read access to all models referenced in the view. 

For example, if I have a <inline-code>headCount<inline-code> view that pulls in data from <inline-code>student<inline-code> and <inline-code>teacher<inline-code>:

Running on the frontend requires read access to both models

Only user roles that have read access to both the <inline-code>student<inline-code> and <inline-code>teacher<inline-code> models will be able to invoke <inline-code>await api.headCount()<inline-code> successfully. Users without the necessary permissions will be served a <inline-code>403 Forbidden<inline-code> response. 

Roles that have access to a view are displayed in the sidebar in the Gadget editor.

In this example, only users with the <inline-code>manager<inline-code> role have permission to access data returned by <inline-code>api.headCount()<inline-code>.

The sidebar also shows you how to run your view, and gives you a link to run it in the API playground or go to the API docs for the view.

You might want to present users with data, such as aggregations, without giving them full read access to a model. In this case, you can wrap your view call in a global action and grant those users permission to the action instead of the models powering the view.

If you’re using server-side rendering with Remix or React Router v7, you don’t need to call the view in a global action. Instead, you can use <inline-code>context.api.actAsAdmin<inline-code> in a <inline-code>loader<inline-code> function to call a view, then return the queried data to the frontend:

Running a view in a Remix/React Router loader

And whether you are running views written in <inline-code>.gelly<inline-code> files or using <inline-code>.view()<inline-code>, you can also make use of the <inline-code>useView<inline-code> React hook in your frontend to manage selection, loading, and any query errors:

Using the useView hook

Learn more

You can find the details and additional sample queries in our view docs.

If you have questions or feedback on how to use views in your projects, you can connect with the Gadget team through our developer Discord community.

Riley Draward
Author
Mohammad Hashemi
Reviewer
Try Gadget
See the difference a full-stack development platform can make.
Create app
No items found.

Introducing views in Gadget: Performant data queries

Run complex serverside queries without compromising on app performance.
Problem
Solution
Result
TLDR: Read, transform, and aggregate data much, much faster with views!

Developers can now offload complex read queries, aggregations, and joins to Gadget’s infrastructure to minimize load times and maximize performance.

Views are used for performing aggregations or transformations across multiple records within one or more models. They allow you to calculate metrics across large datasets, join data across multiple models, and simplify the interface for running these complex queries.

For example, you could power a dashboard and calculate the total number of students and teachers for a given city, and list the available courses:

api/views/educationMetrics.gelly

Without views, you would need to manually fetch, paginate, count, and aggregate records in your backend. Execution time could balloon as your number of records grows. Views pushes this work down to the database and returns results much faster than manual aggregation.

Out of the box, views include support for parameter inputs, result selection and aliasing, and pagination for when a query includes more than 10,000 returned records.

When processing large amounts of data, developers are often stuck relying on slow, resource-intensive read operations, or re-writing the same queries over and over again. With views, you don’t need to worry about managing database load or carefully optimizing each query for performance, because Gadget handles all of that for you.

A better way to query data

Views are read-only queries executed on a fleet of high-performance read replicas optimized for executing these queries. Your views are converted to performant SQL automatically generated by Gadget thanks to our deep insight into the shape of your data models. 

You don’t need to manually set up read replicas or worry about query routing — Gadget views handle all of this out of the box. And your big, expensive view executions won’t interrupt normal query processing for the rest of your application, which is a major time saver and performance win for developers.

Views can even be run in the API playground which makes for easy building, testing, and experimentation.

Getting started with views

Views are written in Gelly, Gadget’s data access language. Gelly is a superset to GraphQL, and provides a declarative way to write queries that are either computed or re-computed across records at the database level, while optimizing for efficiency across a high number of rows. 

Although it’s similar to SQL and GraphQL, it provides developers more flexibility by allowing for things like relationship traversals, reusable fragments, and more ergonomic expressions. It comes with some quality of life improvements over alternative languages, and eliminates some of the minor annoyances like requiring trailing commas in plain old SQL.

Views can be saved into a <inline-code>.gelly<inline-code> file or run with <inline-code>.view()<inline-code> in any namespace in your app’s API client (or GraphQL API).

When a view is saved in a <inline-code>.gelly<inline-code> file, that view is automatically added to your app’s API. A view saved in <inline-code>api/views/getStudentMetrics.gelly<inline-code> can be executed with <inline-code>await api.getStudentMetrics()<inline-code>, and <inline-code>api/models/shopifyProduct/views/getProductTotals.gelly<inline-code> is run with <inline-code>await api.shopifyProduct.getProductTotals();<inline-code>.

Running a named view from the API client

When building views in the API playground, you can use <inline-code>.view()<inline-code> to execute inline queries. The <inline-code>.view()<inline-code> function is available on all namespaces in your app. For example, to get some aggregate data on the number of comments for a blog, you could run:

Running an inlne view from the API client

Named vs inline views

We recommend writing your views in named <inline-code>.gelly<inline-code> files when possible. This enables you to easily call the view using your API client, gives you better insight into access control permissions for the query, and allows Gadget to lint your views for errors.

There are still good uses for running inline views using the <inline-code>.view()<inline-code> API:

  • You are building your view using the API playground. Instead of writing in a <inline-code>.gelly<inline-code> file and running the action in the playground to test, you can inline everything in the playground.
  • You are building a view dynamically, and change the shape of the view query based on external criteria. For example, a user might be able to add and select custom fields to be included in a view.

Run queries from your frontend and backend

Your views can be run in both your Gadget backend and frontend, but it is important to note that frontend use requires the user’s role to have read access to all models referenced in the view. 

For example, if I have a <inline-code>headCount<inline-code> view that pulls in data from <inline-code>student<inline-code> and <inline-code>teacher<inline-code>:

Running on the frontend requires read access to both models

Only user roles that have read access to both the <inline-code>student<inline-code> and <inline-code>teacher<inline-code> models will be able to invoke <inline-code>await api.headCount()<inline-code> successfully. Users without the necessary permissions will be served a <inline-code>403 Forbidden<inline-code> response. 

Roles that have access to a view are displayed in the sidebar in the Gadget editor.

In this example, only users with the <inline-code>manager<inline-code> role have permission to access data returned by <inline-code>api.headCount()<inline-code>.

The sidebar also shows you how to run your view, and gives you a link to run it in the API playground or go to the API docs for the view.

You might want to present users with data, such as aggregations, without giving them full read access to a model. In this case, you can wrap your view call in a global action and grant those users permission to the action instead of the models powering the view.

If you’re using server-side rendering with Remix or React Router v7, you don’t need to call the view in a global action. Instead, you can use <inline-code>context.api.actAsAdmin<inline-code> in a <inline-code>loader<inline-code> function to call a view, then return the queried data to the frontend:

Running a view in a Remix/React Router loader

And whether you are running views written in <inline-code>.gelly<inline-code> files or using <inline-code>.view()<inline-code>, you can also make use of the <inline-code>useView<inline-code> React hook in your frontend to manage selection, loading, and any query errors:

Using the useView hook

Learn more

You can find the details and additional sample queries in our view docs.

If you have questions or feedback on how to use views in your projects, you can connect with the Gadget team through our developer Discord community.

Interested in learning more about Gadget?

Join leading agencies making the switch to Gadget and experience the difference a full-stack platform can make.