Close

Sign up to Gadget

Sign Up

Type Safe JavaScript API Clients on All Projects!

Harry Brundage
October 10, 2021

Every Gadget project comes with a rich, autogenerated GraphQL API that you use to query and mutate data stored in Gadget. However, interacting with a raw GraphQL API can be burdensome. You need to implement authentication, data hydration, pagination and, for TypeScript users, type generation.

To address these challenges, we’re announcing that, starting today, every Gadget project has its own purpose-built, type safe Javascript API client library. Your application’s API client can be installed as an npm module or sourced with a <inline-code><script/><inline-code> tag. This API gives you easy functions for running queries and mutations on your data.

Your client will

  • Hydrate data on its way to or from your app, letting you work with JS objects like <inline-code>Date<inline-code> instead of dates as strings
  • Paginate any list of records from your app with one simple <inline-code>nextPage()<inline-code> or <inline-code>.previousPage()<inline-code> call
  • Authenticate using any of Gadget’s supported authentication schemes
  • Make bulk API calls to your backend
  • Typecheck inputs parameters and query responses against the schema defined in your Gadget application

Let’s take a look at an example. For my simple blog application, I can install the generated JavaScript client by adding Gadget’s npm registry, and then installing my module:


npm config set @gadget-client:registry https://registry.gadget.dev/npm
npm install @gadget-client/simple-blog-example

Once I have my client, I’ll instantiate it and start making API calls:


import { Client } from "@gadget-client/simple-blog-example";

const api = new Client();

// find a list of posts from the Post model in the Gadget backend
const posts = await api.post.findMany({ sort: { createdAt: "Descending" } });
console.log(posts.map(post => post.title));

// create a new Post record
const newPost = await api.post.create({
  post: {
    title: "Hello World",
    body: { markdown: "# Hello World!\nNice to meet you." },
    author: { _link: "1" },
  },
})

console.log(“created new post”, newPost.id);

Like GraphQL, the generated client supports dynamic selections of different properties when querying data. For example, let’s say that our Post model Belongs To an Author model in our blog. if you want to select the blog post’s id, the post title, and author’s name of each post, you can use the <inline-code>select<inline-code> option:


await api.post.findMany({ select: { id: true, title: true, author: { name: true } } });

This query will return just the fields you selected, just like GraphQL, ensuring minimal, fast communication between the server and the client.

Unlike most other clients, our selections are fully typed with TypeScript! If you’re using an editor that supports TypeScript, your editor will know what fields are present on the returned records, and can warn you if your properties aren’t part of the selection :

The <inline-code>select<inline-code> option is typed, preventing you from selecting fields that don’t exist:

As a developer using Gadget, you no longer need to configure a code generator or build step to get this type safety. Your API client package has these types built in, ready to use, just like that.

For more examples on what you can build using the Shopify Connection, check out these additional blog posts:

Keep reading to learn about how it's built

Under the hood

The API client library for your application has one job: hide the accidental complexity of communication so that you can spend your time on what really matters. In order to make it easy to use, we’ve made Gadget’s client feel just like any other SaaS platform’s API client -- familiar, because it uses the nouns and verbs of the domain and helpful, because it knows what inputs are valid and invalid for each specific API call.

This is a bit trickier for Gadget because we don’t have 5 hardcoded models we can write specific APIs and TypeScript types for -- you defined your own models! So, in your API client, the names of the functions and the groupings are not static, they’re derived from your models and actions. Same with the types: valid inputs and outputs are different for each model.

One option would have been to build a generic client without types that treat each bit of dynamism as a parameter. Something like:


await api.modelManagerFor(“posts”).update(1, {setStatus: {published: true}})

You deserve better. Taking inspiration from the code generators before us like Prisma and graphql-code-generator, your Gadget API client now looks like this:


await api.post.publish()

This is a big decision because it means that instead of publishing one universal API client library to npm, Gadget generates and hosts an api clients for each application powered by Gadget.

We want you to install this package the same way you install all others, via an npm registry,so we built out a dynamic one! When you <inline-code>npm install<inline-code> your Gadget client, the <inline-code>npm<inline-code> client makes a request to our registry at <inline-code>registry.gadget.dev<inline-code>, and we generate an up to date version of your API client. We store this package under a stable version ensuring you always install the same package with the same version e. Over time, as your version changes  the package evolves to match your backend application. Updating your API client works just like any other package update -- you bump the version in package.json or have a tool do it for you.