The ChiselStrike TypeScript client API provides a typesafe SDK generated from your backend data model, which you can use for CRUD operations.
We are happy to announce the release of ChiselStrike 0.15, bringing our developers a TypeScript client API for use in web applications and Node.js programs. We have always had an automatically generated REST API derived from your data model, and this client now makes it much easier to work with, especially with single page apps. It was designed specifically to work with TanStack Query, a popular tool for managing queries and improving their performance in your web applications.
Applications built on ChiselStrike typically declare one or more entities to describe its data model. Entities are simply TypeScript classes added to special models
source directory, like this:
import { ChiselEntity } from '@chiselstrike/api';
export class BlogPost extends ChiselEntity {
author: string = 'Anonymous';
content: string;
publishedAt: number;
hidden: boolean;
}
Then, to expose a fully RESTful CRUD API for that entity, this code goes in the routes
directory (in this case, a file called posts.ts
— this name is important as we'll see later):
import { BlogPost } from '../models/BlogPost';
export default BlogPost.crud();
This is all the code required to start reading and writing the BlogPost entity over HTTP. But it generally requires a lot of client code to build all the URLs with query strings and parse the JSON responses. The JSON loses all type data about BlogPost and its properties as well. We want to remove all that boilerplate and also provide strongly typed BlogPost data in response. That's what the new client library does.
In order for the client code to have proper types for the BlogPost entity, we need to generate some bespoke code based on the entity class above. The ChiselStrike CLI can now do this for you. First your local dev server must be running in a shell:
$ npm run dev
Then, in another shell, run the following command and choose a directory where the TypeScript source will be written:
$ npm exec chisel generate [path/to/dir]
chisel generate
queries the running server for active entities and routes, and generates several files in the chosen directory, most of which you do not need to read. The important parts are the generated type for BlogPost in models.ts
:
export type BlogPost = {
id: string;
author: string;
content: string;
publishedAt: number;
hidden: boolean;
};
And the factory function for the client in client.ts
:
export function createChiselClient(config: ClientConfig);
There will be one generated type for every entity that's associated with a route.
Let's say you have a React app, and you generated the above files into its src/chisel
directory. Now you can import client.ts
and models.ts
and start using them against the local dev server. Here is what it looks like if you want to query for all BlogPost instances and put them into an array:
import { Message } from './chisel/models';
import { createChiselClient } from './chisel/client';
const chiselClient = createChiselClient({
serverUrl: 'http://localhost:8080',
});
const posts: BlogPost[] = await chiselClient.posts.getAll();
Notice that there is a property called posts on the chiselClient
object returned by createChiselClient
. That property exists because of the route declared in the source file posts.ts defined above. There will be one such property for every route that chisel generate
discovers from the running server. Internally, the client's getAll
function does the magic of mapping the JSON response from the REST API into BlogPost objects with proper types.
Of course, the call to getAll
should be in a hook, and you would use it in a JSX template to populate a component.
If you aren't using TanStack Query yet, you should! It takes care of a lot of the heavy lifting that makes database queries easy and efficient. The ChiselStrike client API works well with it. Here's what a React component might look like using both TanStack and ChiselStrike in a minimal way:
function usePosts() {
return useQuery(["posts"], () => {
// Here we use the ChiselStrike API in the TanStack Query function
return chiselClient.messages.getAll()
})
}
function PostsComponent() {
const { data, status, isLoading, error } = usePosts()
if (isLoading) return <div>Loading</div>
// data is now an array of BlogPost, or undefined
return (
<div>
{ data?.map(post => (
<div key={ post.id }>
<div>{ post.author }</div>
<div>{ post.content }</div>
<div/>
))}
</div>
);
}
function App() {
return (
<div className="App">
<QueryClientProvider client={queryClient}>
<PostsComponent/>
</QueryClientProvider>
</div>
)
}
TanStack's useQuery
hook takes a query function, inside which we simply use the ChiselStrike client API to return a promise that becomes fulfilled with an array of BlogPost objects to render. TanStack preserves the type of the array so that the resulting data object has strongly typed BlogPost objects to iterate.
The client API has full support for the various HTTP methods supported by ChiselStrike and wraps them all up in an easy-to-use API. It also understands any entity relationships you might have established in your model.
The API internally uses the standard web fetch API to perform HTTP requests. This means that the generated TypeScript also works in Node.js environments where the fetch API is available. This is great if you're using Node 18 to prerender your site, as you can use the very same client code to query for the content to render.
For comprehensive documentation and sample code, check out the client API documentation. And if you build something with ChiselStrike, please let us know on our Discord or Twitter. Happy chiseling!