In past, I suffered a lot of the pain of maintaining a schema or some sort of type definition in my typescript applications.
If your frontend and backend are written in typescript and you suffered the same headaches, this article is for you.
In some projects with Swagger I generated schemas based on .yaml files 😢, but when you create new features is quite common to change API(s) so you end up with a lot of definitions/schemas generations. 😅
It's the same with graphql because you need to learn graphql language to describe your API(s) and then every time you change something you have to generate the updated API definitions/schemas.
Up to here only bad news 😂 but fortunately you can solve all these pains with tRPC.
Getting started
The best way to start with tRPC is using the Next.js example from the tRPC GitHub repository because is created and maintained by the core team and follows the best practices.
Here we can test the solution with a fully configured project with:
Requirements
- Node >= 14
- Docker up and running in your local machine (for Postgres)
Next.js example
With these commands you can start from scratch with the application:
yarn:
yarn create next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
cd trpc-prisma-starter
yarn
yarn dx
npm:
npx create-next-app --example https://github.com/trpc/trpc --example-path examples/next-prisma-starter trpc-prisma-starter
cd trpc-prisma-starter
yarn
yarn dx
Then after the initial setup at http://localhost:3000 you can add new posts and read the post text with the View more
link.
So far so good! 💪
Show me the code
Soon the tRPC team will release the new version 10 anyways these concepts are always good.
Server
Let's focus on these files in our server codebase
Here we have a function to create our router and as you can see we are using trpc.router
straight from @trpc/server
library.
- /src/server/createRouter.ts
Similar to GraphQL, tRPC makes a distinction between query and mutation endpoints. Let's add an add
mutation:
- /src/server/routers/post.ts
mutation needs a few parameters:
-
name
: string: The name of this endpoint -
input
: Optional. This should be a function that validates/casts the input of this endpoint and either returns a strongly typed value (if valid) or throws an error (if invalid). Alternatively, you can pass a Zod, Superstruct, or Yup schema. -
resolve
: It's a function with a singlereq
argument, the validated input is passed intoreq.input
. This is the actual implementation of the endpoint, you can use prisma or you can write your logic. Here we are usingprisma.post.create
to save our data in the database.
Here we define a router somewhere in our server code base.
.merge
function (line 7) helps us to split endpoints into multiple files and this is a best practice to obtain a clean code.
Finally, we export the AppRouter type, typescript infers to this type the right shape ready to be used in our client.
- /src/server/routers/_app.ts
Client
The Next.js application needs to be configured to work with tRPC. First thing first we need to wrap the whole application with withTRPC
higher-order component
- /src/pages/_app.tsx
Then we need to create a set of strongly-typed hooks using your API's type signature import type { AppRouter } from '~/server/routers/_app';
- src/utils/trpc.ts
Then we can make an API request
- src/pages/index.tsx
As you can see we are using the hook we created before to call the mutation.
Aha moment 🤯
Now I simulate a change in my API definition
- changed
add
toadd_new
- added
newField
inside input object
Wow 🤩 Typescript interface is smart enough to complain immediately about that
Final thought
This library is very convenient when:
- you have private APIs and you don't need documentation to share with the rest of the world
- you have a full stack typescript project
I like it because it's fast and intuitive
I hope this article convinces you to give it a try.
You can follow me on Twitter, where I'm posting or retweeting interesting articles.
See ya! 👋
Top comments (1)
Great solution! 😎👌🏻