Building Frontends

Gadget applications can host user interfaces for the people using your application. Using HTTP Routes, you can render HTML pages or other static files which can be accessed by anyone with a web browser. Frontends hosted on Gadget can use data from Gadget models to serve dynamic content and can be extended using plugins from the fastify.

Picking a frontend technology stack

There are two main different ways to build frontends for the modern web:

  • server-rendered HTML with light JavaScript enhancement
  • client-rendered HTML with JavaScript frameworks and optional server-side rendering

Gadget applications currently support rendering HTML server-side. If you want to use React, Vue, or other client-side oriented frameworks, see the Single page JS Apps section below.

Looking for help deploying your frontend?

Follow our deployment documentation for more information.

Server side HTML

Gadget apps are built on Fastify, which has a whole ecosystem of plugins for rendering HTML server-side to send back to the browser. See the Fastify ecosystem page for a list of plugins.

Gadget is currently building an easy-to-use views system for rendering HTML server-side. If you're interested, join us on Discord and let's discuss!

Single page JS apps

Building rich or complicated frontend experiences often merit using a frontend JavaScript framework like React or Vue. Gadget doesn't have support for hosting client-side applications built right in. Instead, Gadget recommends using your app's GraphQL API within one of the great toolchains for building and deploying client-side apps out there today.

For more information on your app's GraphQL API, see the API Reference.

React apps

Gadget has a library for making API calls to your app from a React application quickly and easily called @gadgetinc/react. It provides React hooks like useFindOne, useFindMany, and useAction that fetch data and run actions. @gadgetinc/react uses the same API client object you might use elsewhere under the hood, and calls the same GraphQL API for your app as everything else.

Using create-react-app

create-react-app is a simple environment for building React applications maintained by the React team. create-react-app works great for structuring a React frontend app that talks to a Gadget backend app over GraphQL, and is easy to deploy on platforms like Vercel or Netlify.

create-react-app apps integrate with Gadget by installing the API Client for your app and the @gadgetinc/react hooks library.

Setting up a create-react-app app

If you don't already have a create-react-app application, you can create one:

npx create-react-app example-app-frontend
# or
yarn create react-app example-app-frontend
# then
cd example-app-frontend

Next, you need to install your Gadget backend app's client package.

To install the Example App node package, register the Gadget NPM registry for the @gadget-client package scope:

npm config set @gadget-client:registry https://registry.gadget.dev/npm

Then, install the Example App client package and the @gadgetinc/react package using your package manager:

npm install @gadget-client/example-app @gadgetinc/react
yarn add @gadget-client/example-app @gadgetinc/react

These instructions are examples for an example app. When you create your own Gadget app, they'll update to reflect the right name and commands for your specific application.

Create your own app at gadget.new

As you make changes to Example App, Gadget will publish new versions of your API client to its NPM registry. See Client Regeneration for more details on when updates are necessary.

With your client package installed, you can instantiate it in any file, but usually we name the file src/api.js:

src/api.js
JavaScript
1import { Client } from "@gadget-client/example-app";
2
3export const api = new Client({
4 environment:
5 process.env["NODE_ENV"] === "production" ? "Production" : "Development",
6 authenticationMode: { browserSession: true },
7});

Once you have the required packages, you must complete the @gadgetinc/react setup and provide the API client to the hooks library. For simplicity, we'll add the Provider from @gadgetinc/react to src/index.js like so:

src/index.js
JavaScript
1// add these imports
2import { Provider } from "@gadgetinc/react";
3import { api } from "./api";
4
5// existing imports
6import React from "react";
7import ReactDOM from "react-dom/client";
8import "./index.css";
9import App from "./App";
10
11const root = ReactDOM.createRoot(document.getElementById("root"));
12root.render(
13 <React.StrictMode>
14 <Provider value={api.connection.currentClient}>
15 <App />
16 </Provider>
17 </React.StrictMode>
18);

With the provider in place, you can now use the @gadgetinc/react hooks library in your app. For example, we can fetch a list of Task records from your app's GraphQL API with the useFindMany hook in the App component:

src/App.js
JavaScript
1import { api } from "./api";
2import "./App.css";
3import { useFindMany } from "@gadgetinc/react";
4
5function App() {
6 const [{ data, error, fetching }] = useFindMany(api.task);
7 a;
8
9 return (
10 <div className="App">
11 <header className="App-header">
12 {fetching && <p>Loading...</p>}
13 {error && <p>Error: {String(error)}</p>}
14 {data && JSON.stringify(data)}
15 </header>
16 </div>
17 );
18}
19export default App;

Your create-react-app front-end is set up and ready for use with your Gadget backend!

Using next.js

Next.js is a popular framework for building React applications that require very little setup to get going, and it works great for building frontends for Gadget applications. It has built-in support for server-side rendering and is straightforward to deploy on platforms like Vercel or Netlify.

next.js apps integrate with Gadget by installing the API Client for your app and the @gadgetinc/react hooks library.

Setting up a next.js app

If you don't already have a next.js application, you can create one with the create-next-app utility:

plain
// or
yarn create next-app
// or
pnpm create next-app

Next, you need to install your Gadget backend app's client package.

To install the Example App node package, register the Gadget NPM registry for the @gadget-client package scope:

npm config set @gadget-client:registry https://registry.gadget.dev/npm

Then, install the Example App client package and the @gadgetinc/react package using your package manager:

npm install @gadget-client/example-app @gadgetinc/react
yarn add @gadget-client/example-app @gadgetinc/react

These instructions are examples for an example app. When you create your own Gadget app, they'll update to reflect the right name and commands for your specific application.

Create your own app at gadget.new

As you make changes to Example App, Gadget will publish new versions of your API client to its NPM registry. See Client Regeneration for more details on when updates are necessary.

With your client package installed, you can instantiate it in any file, but usually, we name the file api.js:

api.js
JavaScript
1import { Client } from "@gadget-client/example-app";
2
3export const api = new Client({
4 environment:
5 process.env["NODE_ENV"] === "production" ? "Production" : "Development",
6 // for client side data access, we don't pass anything and the client will default to using the browser session authentication mode
7 // for server side data access, pass an API key by uncommenting the line below
8 // authenticationMode: { apiKey: "gsk-some-api-key-here" }
9});

Once you have the required packages, you must complete the @gadgetinc/react set-up to provide the API client to the hooks library. React providers in next.js are usually added in the _app.js file like so:

pages/_app.js
JavaScript
1import { Provider } from "@gadgetinc/react";
2import { api } from "../api";
3
4function MyNextApp({ Component, pageProps }: AppProps) {
5 return (
6 <Provider value={api.connection.currentClient}>
7 <Component {...pageProps} />
8 </Provider>
9 );
10}
11
12export default MyNextApp;

With the provider in place, you can now use the @gadgetinc/react hooks library in your next.js pages. For example, we can fetch a list of Task records from your app's GraphQL API with the useFindMany hook:

pages/index.jsx
JavaScript
1import { useFindMany } from "@gadgetinc/react";
2import { TaskCard } from "../components/TaskCard";
3
4const Home: NextPage = () => {
5 const [{ data, fetching, error }] = useFindMany(api.task, {
6 sort: { createdAt: "Descending" },
7 first: 30,
8 });
9
10 return (
11 <div>
12 <h1>Posts</h1>
13 {fetching && <div class="spinner" />}
14 {error && <div status="error">{error.message}</div>}
15 {data && data.map((task) => <TaskCard key={task.id} task={task} />)}
16 </div>
17 );
18};
19
20export default Home;

You can find more examples of next.js frontends built using Gadget in the Gadget examples repo.

Server-side vs client-side API access

Next.js supports two main ways of accessing data:

  • requests made by the node.js process running server-side in a getStaticProps or getServerSideProps function
  • requests made by the browser running client-side in a React hook, like use-fetch, react-query, urql or @gadgetinc/react.

Your app's GraphQL API supports both of these methods of data access. In general, client-side data access is fastest for the user, as their browser isn't making extra requests to a server-side process that then makes requests to a Gadget API. Client-side access also allows the API to customize responses for the user to limit data access permissions and to power things like logged in/logged out state. However, server-side data access allows further data processing and is sometimes necessary to power things like next.js' [getStaticPaths] for static site generation.

Gadget generally recommends client-side data access for your next.js applications.

Client-side data access

For accessing data on the client, Gadget recommends using the @gadgetinc/react hooks library:

/api.js
JavaScript
1import { Client } from "@gadget-client/example-app";
2
3// instantiate a client with the auth powered by each user's individual browser (the default)
4export const api = new Client({
5 environment:
6 process.env["NODE_ENV"] === "production" ? "Production" : "Development",
7});

Then, after wrapping our app in the <Provider/> from @gadgetinc/react, we can use React hooks to access data:

pages/index.jsx
JavaScript
1import { api } from "../api";
2import { useFindMany } from "@gadgetinc/react";
3import { TaskCard } from "../components/TaskCard";
4
5const Home: NextPage = () => {
6 // use a React hook for data access within the component
7 const [{ data, fetching, error }] = useFindMany(api.task);
8 // ...
9 return (
10 <div>
11 {data.map((task) => (
12 <TaskCard key={task.id} task={task} />
13 ))}
14 </div>
15 );
16};
Server side data access

For accessing data on the server, Gadget recommends using the standard, imperative style API client object, without hooks from @gadgetinc/react. It's easiest to instantiate your Gadget API client with an API key for authentication, and then use it when generating server-side props.

/api.js
JavaScript
1import { Client } from "@gadget-client/@gadget-client/example-app";
2
3// instantiate a client with an API key that will only be used server-side
4export const api = new Client({
5 environment:
6 process.env["NODE_ENV"] === "production" ? "Production" : "Development",
7 authenticationMode: { apiKey: "gsk-some-api-key-here" },
8});

Then in our next.js pages, we can make API calls using the api.model.findMany style finders built into the api client object, and send the results as props to the client-side component:

pages/index.jsx
JavaScript
1import { api } from "../api";
2import { TaskCard } from "../components/TaskCard";
3
4const Home: NextPage = (props) => {
5 // use props passed from the getServerSideProps function
6 return (
7 <div>
8 {props.tasks.map((task) => (
9 <TaskCard key={task.id} task={task} />
10 ))}
11 </div>
12 );
13};
14
15export const getServerSideProps = async () => {
16 return {
17 props: {
18 tasks: await api.task.findMany({}),
19 },
20 };
21};

Using a Gadget API key to initialize your Gadget client on the server gives you full read or write access to the database. If working on a Shopify app, shop tenancy is not enforced.

This key should never be exposed in a browser, and users should never be able to see it. This should only be used when connecting to another secure server.

Hosting frontends

Frontends built using React or other client-side frameworks need to be deployed to a hosting provider. Gadget recommends using Vercel or Netlify for hosting your frontends, as both make it easy to deploy and work great with existing Gadget tooling. You can find more information on how to deploy a frontend application by viewing our deployment documentation.

You must register the Gadget npm registry in all environments where you want to use this package, which would include production systems, continuous integration environments, or build steps like Vercel or Netlify's static site builders. This can be done using the above npm config command, or by writing out an .npmrc file in those environments that points to Gadget for the @gadget-client scope.

To indicate to hosting platforms where to find the @gadget-client/example-app package, create an .npmrc file in the web/frontend directory with the following content:

web/frontend/.npmrc
registry=https://registry.npmjs.org/
@gadget-client:registry=https://registry.gadget.dev/npm

Shopify Embedded Apps

If you're building a Shopify application, Shopify recommends you build it in React using their Polaris component library, and the App Bridge for communicating with the Shopify platform. Gadget has an easy-to-use library for building React frontends for Shopify with Polaris. Read more in the Shopify App Frontend guide.

Gadget also has an example Shopify Embedded App built with next.js you can use as a start in the examples repo.

Other frameworks

Gadget currently doesn't have specific adapter libraries built for client-side frameworks other than React, like Vue, Svelte, or Solid. However, your Gadget application has a rich GraphQL API ready to read and write data, which means you can use any existing GraphQL client for these frameworks to interface with your Gadget app right out of the box.

urql for Vue and Svelte

Gadget recommends urql as a high quality GraphQL client with built in support for Vue and Svelte. Your generated JS API Client package uses urql under the hood, so it's easy to re-use the existing API authentication and connectivity code from the Gadget API client within a Vue or Svelte app.

For example, we can wire up a generated api Client instance for use within a Vue app by accessing the api.connection.currentClient urql.Client instance for use in Vue:

JavaScript
1// import the urql vue bindings
2import { provideClient } from "@urql/vue";
3// import and instantiate your Gadget API client, which uses urql under the hood
4import { Client } from "@gadget-client/example-app";
5
6// create an instance of the Gadget API Client
7// Note: requests will be unauthenticated - see https://docs.gadget.dev/guides/access-control for more info
8export const api = new Client({
9 environment:
10 process.env["NODE_ENV"] === "production" ? "Production" : "Development",
11});
12
13// provide the pre-configured urql client instance from the Gadget API Client to the vue bindings
14provideClient(api.connection.currentClient);

Read more about @urql/vue in urql's docs.

For Svelte support, you can also use the generated Gadget API Client to set up the @urql/svelte bindings:

JavaScript
1// import the urql svelte bindings
2import { setContextClient } from "@urql/svelte";
3// import and instantiate your Gadget API client, which uses urql under the hood
4import { Client } from "@gadget-client/example-app";
5
6// create an instance of the Gadget API Client
7// Note: requests will be unauthenticated - see https://docs.gadget.dev/guides/access-control for more info
8export const api = new Client({
9 environment:
10 process.env["NODE_ENV"] === "production" ? "Production" : "Development",
11});
12
13// provide the pre-configured urql client instance from the Gadget API Client to the svelte context bindings
14setContextClient(api.connection.currentClient);

Read more about @urql/svelte in urql's docs.

Other libraries

Because your Gadget app's GraphQL API is spec compliant, any GraphQL client can be used to make requests to it. You can use libraries like graphql-request in JS, apollo-client for JS, Swift, and Kotlin, or even any HTTP request library that can send and receive JSON.

For example, we can make an API call to Gadget using the cURL command line utility:

Shell
curl -X POST \
-H "Content-Type: application/json" \
-d '{ "query": "query { gadgetMeta { name } }" }' \
https://example-app.gadget.app/api/graphql