<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Midoriya</title>
    <description>The latest articles on Forem by Midoriya (@quangnmwork).</description>
    <link>https://forem.com/quangnmwork</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F865995%2F7d1e6ee7-160f-4afc-b044-b7a0c56ea92f.jpeg</url>
      <title>Forem: Midoriya</title>
      <link>https://forem.com/quangnmwork</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/quangnmwork"/>
    <language>en</language>
    <item>
      <title>Typesafe API with TS-Rest, React Query</title>
      <dc:creator>Midoriya</dc:creator>
      <pubDate>Tue, 26 Dec 2023 06:47:13 +0000</pubDate>
      <link>https://forem.com/quangnmwork/typesafe-api-with-ts-rest-react-query-4ao6</link>
      <guid>https://forem.com/quangnmwork/typesafe-api-with-ts-rest-react-query-4ao6</guid>
      <description>&lt;p&gt;Hi, today I'll introduce you to a library called ts-rest. A library that supports API organization while integrating with the typesafe API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is TS-Rest ?
&lt;/h2&gt;

&lt;p&gt;In short, TS-Rest is a library help you define your API and give you an end to end safe API.&lt;/p&gt;

&lt;p&gt;In addition, TS-Rest can help you define API normally (you can define Put, Patch, Delete, Get, Post Method).&lt;/p&gt;

&lt;p&gt;If you work with a Third Party API. TS-Rest is a good choice to leverages TypeScript's type system to provide strong typing for API endpoints, request/response payloads, and data models. This ensures that you're working with the correct data types and minimizes the risk of runtime errors due to incorrect data handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to use TS-Rest ?
&lt;/h2&gt;

&lt;p&gt;In this part, I will be creating a simple Todo app to introduce TS-Rest on the client side.&lt;/p&gt;

&lt;p&gt;In this project, I will use CRA for initilizing my React app.&lt;br&gt;
These are libraries I will use in this project : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React Query &lt;/li&gt;
&lt;li&gt;@ts-rest/core for creating contract,router&lt;/li&gt;
&lt;li&gt;@ts-rest/react-query &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let's get started 🚀&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Installation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before we're going to project please make sure you have installed @ts-rest library and react-query library.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;yarn add @ts-rest/react-query @tanstack/react-query @ts-rest/core&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup fake API server&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In this project I will use json-server for creating fake API server. &lt;/p&gt;

&lt;p&gt;Steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install json-server : &lt;code&gt;yarn add json-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Next create a db.json in your project
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "data": [
    {
      "title": "Todo 1",
      "id": 1
    },
    {
      "title": "todo 2",
      "id": 2
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;package.json&lt;/code&gt; add this script:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;scripts : {
  .....
  "server": "json-server --watch db.json --port 5000"
  .....
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So let's run &lt;code&gt;yarn server&lt;/code&gt;. This will start out server in this project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup ts-rest router&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First create &lt;code&gt;src/constants/index.ts&lt;/code&gt; for defining some API Route and Server URL.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/constants/index.ts

export const SERVER_URL = 'http://localhost:5000/data';

export const API_ROUTE = {
  GET: '/',
  CREATE: '/',
  UPDATE: '/:id',
  DELETE: '/:id',
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, create &lt;code&gt;lib/ts-rest&lt;/code&gt; folder in your src directory. Inside that we will create two file :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//lib/ts-rest/router.ts

import { initContract } from '@ts-rest/core';
import { API_ROUTE } from '../../constants';
import { z } from 'zod';

const c = initContract();

export const router = c.router({
  getAllTodo: {
    method: 'GET',
    path: API_ROUTE.GET,
    responses: {
      200: z.array(
        z.object({
          id: z.number(),
          title: z.string(),
        }),
      ),
    },
  },
  createTodo: {
    method: 'POST',
    path: API_ROUTE.CREATE,
    responses: {
      201: z.object({
        id: z.number(),
        title: z.string(),
      }),
    },
    body: z.object({
      title: z.string(),
    }),
  },
  updateTodo: {
    method: 'PATCH',
    path: API_ROUTE.UPDATE,
    responses: {
      204: z.object({
        id: z.number(),
        title: z.string(),
      }),
    },
    pathParams: z.object({ id: z.number() }).required(),
    body: z.object({
      title: z.string(),
    }),
  },
  deleteTodo: {
    method: 'DELETE',
    path: API_ROUTE.DELETE,
    responses: {
      200: z.object({
        id: z.number(),
        title: z.string(),
      }),
    },
    pathParams: z.object({ id: z.number() }).required(),
    body: z.undefined(),
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code is simply the define route of API.&lt;br&gt;
For each property in router we have : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;method :  Definition of that API's method&lt;/li&gt;
&lt;li&gt;path : Definition of that API's path&lt;/li&gt;
&lt;li&gt;reponses : Definition of that API's return data type (for example : in getAllTodo's response when fetch data success. It's will return an array have type &lt;code&gt;{id:number;title:string}[]&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;pathParams : Definition of that API's path param type. If that API Route uses path params, you must define this. For example in &lt;code&gt;deleteTodo&lt;/code&gt;'s path I have declared the path is &lt;code&gt;/:id&lt;/code&gt;. So I need create a pathParams like this &lt;code&gt;z.object({ id: z.number() }).required()&lt;/code&gt;. (If you define your path like this : &lt;code&gt;/:idPost&lt;/code&gt; your pathParams must be &lt;code&gt;z.object({ idPost: z.number() }).required()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;body : Definition of that API's body type.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally in &lt;code&gt;lib/ts-rest/client-provider&lt;/code&gt; ,let create a clientProvider to query and mutate data from server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { initQueryClient } from '@ts-rest/react-query';
import { router } from './router';
import { SERVER_URL } from '../../constants/';

export const clientProvider = initQueryClient(router, {
  baseUrl: SERVER_URL,
  baseHeaders: {},
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you want custom your api (override). You can use &lt;code&gt;api&lt;/code&gt; property in initQueryClient's options. Please refer &lt;a href="https://ts-rest.com/docs/core/custom"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that's all. We have configured our TS-Rest client provider completely.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup react-query and create some component&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First in &lt;code&gt;App.tsx&lt;/code&gt; let's setup &lt;code&gt;react-query&lt;/code&gt; provider.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { Toaster } from 'react-hot-toast';

const queryClient = new QueryClient()

export const App = () =&amp;gt; {
  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;div className='flex flex-col items-center py-10'&amp;gt;
        &amp;lt;InputTodo /&amp;gt;
        &amp;lt;TodoList /&amp;gt;
        &amp;lt;Toaster /&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create &lt;code&gt;TodoList,InputTodo&lt;/code&gt; component&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/components/TodoList.tsx

import { clientProvider } from "../lib/ts-rest/client-provider"
import TodoItem from "./TodoItem"

const TodoList = () =&amp;gt; {
  const { data, isLoading, } = clientProvider.getAllTodo.useQuery(['GET_ALL_TODO'])

  return (
    isLoading ? &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt; :
      &amp;lt;div className="flex flex-col"&amp;gt;
        {data?.body.map(todoItem =&amp;gt; &amp;lt;TodoItem {...todoItem} key={todoItem.id} /&amp;gt;)}
      &amp;lt;/div&amp;gt;
  )
}

export default TodoList
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/components/TodoList.tsx

import { clientProvider } from "../lib/ts-rest/client-provider"
import TodoItem from "./TodoItem"

const TodoList = () =&amp;gt; {
  const { data, isLoading, } = clientProvider.getAllTodo.useQuery(['GET_ALL_TODO'])

  return (
    isLoading ? &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt; :
      &amp;lt;div className="flex flex-col"&amp;gt;
        {data?.body.map(todoItem =&amp;gt; &amp;lt;TodoItem {...todoItem} key={todoItem.id} /&amp;gt;)}
      &amp;lt;/div&amp;gt;
  )
}

export default TodoList
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;TodoList&lt;/code&gt; component we have use clientProvider to query the data from server. Thanks to Ts-Rest, every time call clientProvider.getAllTodo you will ensure the Safe API, making it easier for maintaining and debugging.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/components/TodoItem.tsx

const TodoItem = ({ id, title }: { id: number, title: string }) =&amp;gt; {
  const { mutateAsync } = clientProvider.deleteTodo.useMutation();
  const queryClient = useQueryClient()

  const onDelete = async () =&amp;gt; {
    try {
      await mutateAsync({
        params: { id }
      })
      queryClient.invalidateQueries({ queryKey: ['GET_ALL_TODO'] })
      toast.success("Delete success")
    } catch (error) {
      toast.error(JSON.stringify(error))
    }
  } 

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;span&amp;gt;{title}&amp;lt;/span&amp;gt;
      &amp;lt;div className="flex gap-3"&amp;gt;
        &amp;lt;button onClick={onDelete}&amp;gt;
          Delete
        &amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also this is an example of using useMutation with TS-Rest. It helps you control the type of pathParams input. &lt;/p&gt;

&lt;p&gt;Finally let's create a &lt;code&gt;InputTodo&lt;/code&gt; component.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const InputTodo = () =&amp;gt; {
  const inputRef = useRef&amp;lt;HTMLInputElement&amp;gt;(null);
  const { mutateAsync } = clientProvider.createTodo.useMutation()
  const queryClient = useQueryClient()

  const onAddTodo = async () =&amp;gt; {
    try {
      if (inputRef.current) {
        await mutateAsync({
          body: {
            title: inputRef.current?.value
          }
        })
        toast.success('Add todo success');
        queryClient.invalidateQueries({ queryKey: ['GET_ALL_TODO'] })

      }
    } catch (error) {
      toast(JSON.stringify(error))
    }
  }

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;input ref={inputRef} placeholder="Add todo..." /&amp;gt;
      &amp;lt;button
        onClick={onAddTodo}&amp;gt;Add&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}

export default InputTodo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to &lt;code&gt;pathParams&lt;/code&gt; TS-Rest can also type safe for body input.&lt;/p&gt;

&lt;p&gt;So's that all 🙏 about demo project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;TS-Rest is great in helping you manage your API Design and easy to maintain.&lt;/p&gt;

&lt;p&gt;However, it still has some points I am not satisfied with Ts-Rest.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is still a library being developed so the community is not much.&lt;/li&gt;
&lt;li&gt;Document has not been written clearly (this is my opinion).&lt;/li&gt;
&lt;li&gt;Currently, it is still not used with React-Query V5. So I hope they will update in the next time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thanks for reading. 👏&lt;/p&gt;

&lt;p&gt;Repo link :  &lt;a href="https://github.com/quangnmwork/ts-rest-tutorial"&gt;https://github.com/quangnmwork/ts-rest-tutorial&lt;/a&gt;&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>react</category>
      <category>nextjs</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
