{"id":22216897,"url":"https://github.com/iamhectorsosa/trpc-basic-starter","last_synced_at":"2026-04-10T23:55:43.812Z","repository":{"id":61586058,"uuid":"548917002","full_name":"iamhectorsosa/trpc-basic-starter","owner":"iamhectorsosa","description":"tRPC Basic Starter","archived":false,"fork":false,"pushed_at":"2022-10-17T09:59:29.000Z","size":133,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-30T06:26:51.266Z","etag":null,"topics":["blog","nextjs","typescript","vercel"],"latest_commit_sha":null,"homepage":"https://trpc-basic-starter.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/iamhectorsosa.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-10-10T11:43:02.000Z","updated_at":"2023-12-05T19:28:12.000Z","dependencies_parsed_at":"2022-10-19T13:30:28.269Z","dependency_job_id":null,"html_url":"https://github.com/iamhectorsosa/trpc-basic-starter","commit_stats":null,"previous_names":["iamhectorsosa/trpc-basic-starter"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamhectorsosa%2Ftrpc-basic-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamhectorsosa%2Ftrpc-basic-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamhectorsosa%2Ftrpc-basic-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/iamhectorsosa%2Ftrpc-basic-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/iamhectorsosa","download_url":"https://codeload.github.com/iamhectorsosa/trpc-basic-starter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245414545,"owners_count":20611367,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["blog","nextjs","typescript","vercel"],"created_at":"2024-12-02T22:13:54.353Z","updated_at":"2026-04-10T23:55:38.784Z","avatar_url":"https://github.com/iamhectorsosa.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Local Setup + Development\n\n```\n$ npx create-next-app --example https://github.com/ekqt/trpc-basic-starter trpc-basic-starter\n$ cd trpc-basic-starter\n\n# \u003c- Using `npm`\n$ npm i\n$ npm run dev\n\n# \u003c- Using `yarn`\n$ yarn dev\n```\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/ekqt/trpc-basic-starter?file=src/pages/index.tsx\u0026file=src/server/routers/user.ts\u0026view=editor)\n\n# tRPC: Understanding Typesafety\n\nTypesafety is the extent to which a programming language prevents type errors. The process of verifying and enforcing the constraints of types may occur at compile time or at run-time. A programming language like [TypeScript](https://typefully.com/) checks a program for errors before execution (at compile time) as a static type checker. In contrast, a library like [Zod](https://zod.dev/) can also provide you type checking at run-time. So how does a library like tRPC helps us better understand typesafety?\n\n\u003e tRPC allows you to easily build and consume fully typesafe APIs, without schemas or code generation.\n\nAt its core, [tRPC](https://trpc.io/) provides the solution to statically type our API endpoints and share those types between our client and server, enabling type safety from end-to-end.\n\n## How does tRPC share types between client/server?\n\nTypes are shared based on one or many _procedures_ contained in Routers. A **procedure** is a _composable_ query, mutation or subscription where you define how your client/server interact with each other.\n\nLet's see what you'd need to create a query procedure for a [Next.js application](https://trpc.io/docs/v10/nextjs). We'll explore these concepts by reviewing our [tRPC-basic-starter](https://github.com/ekqt/trpc-basic-starter) GH repo. Here's how our file structure initially looks like:\n\n```graphql\n# @path: ./src\n├── pages\n│   └── api/trpc\n│       └── [trpc].ts # \u003c-- tRPC HTTP handler\n│   └── _app.tsx      # \u003c-- tRPC Provider\n│   └── index.tsx\n│   └── [...]\n├── server\n│   └── routers\n│       └── _app.ts   # \u003c-- Main app router\n│       └── user.ts   # \u003c-- User sub-router\n│       └── [...]     # \u003c-- More sub-routers\n│   └── trpc.ts       # \u003c-- Procedure helpers\n├── utils\n│   └── trpc.ts       # \u003c-- Typesafe tRPC hooks\n```\n\nYou could define all of your procedures within the **tRPC HTTP handler** and completely skip the server directory. However, this wouldn't be a scalable approach. It's very likely that your backend will require several endpoints, for which it is recommended to separate your procedures into different sub-routers and merge them as suggested in the file structure above.\n\nTo create a query procedure, at the most basic level we need to define an `input` (optional and validated with your library of choice), and a `query` (the actual implementation of the procedure) which runs a function returning the data you need. At the end, the types of each router are exported to provide a fully-typed experience on the client without importing any server code.\n\n```typescript\n// @path: ./src/server/routers/user.ts\nimport { t } from \"../trpc\";\nimport { z } from \"zod\";\n\nexport const userRouter = t.router({\n    // Define a procedure (function) that\n    // ...takes an input and provides a query\n    // ...as `user.greet.useQuery()`\n    greet: t.procedure\n        // Input validation\n        .input(z.object({ name: z.string() }))\n        .query(({ input }) =\u003e {\n            // Here you would process\n            // any information you'd need\n            // ..to return it to your client\n            return { text: `Hello, ${input.name}!` };\n        }),\n});\n```\n\n## Using your new tRPC-backend on the client\n\n[@tRPC/react](https://trpc.io/docs/v10/react-queries) provides a set of hooks wrapped around [@tanstack/react-query](https://tanstack.com/query/v4/docs/guides/queries), so under the hood, they work just the same to fetch data from a server. You'll notice that the conventional _querying keys and functions_ are defined within your procedure.\n\n```tsx\n// @path: ./src/pages/index.tsx\nimport { trpc as t } from \"../utils/trpc\";\n\nexport default function Home() {\n    // Wrapped around @tanstack/react-query\n    // Can also destructure to access\n    // isLoading, isError, isSuccess, error and data\n    const result = t.user.greet.useQuery({ name: \"Client\" });\n\n    if (result.isLoading) {\n        return (\n            \u003cdiv\u003e\n                \u003ch1\u003eLoading...\u003c/h1\u003e\n            \u003c/div\u003e\n        );\n    }\n\n    return (\n        \u003cdiv\u003e\n            \u003ch1\u003e{result.data?.text}\u003c/h1\u003e\n        \u003c/div\u003e\n    );\n}\n```\n\n...and that's the basic setup. Both the result and input are type-inferred from the procedures as defined and will get **TypeScript autocompletion and IntelliSense** that matches your backend API without requiring any code generation.\n\n## More examples of tRPC usage\n\nLet's create an additional sub-router `@path: ./src/server/routers/post.ts` where we need to provide our client with the following: (a) fetch all posts, (b) fetch post by ID, (c) create a new post. Notice that requirements for `a` and `b` are different than for `c`, as the first two are query procedures and the last one requires a **mutation procedure**. However, you will notice that there is _no difference_ between queries and mutation apart from semantics.\n\n```typescript\nimport { t } from \"../trpc\";\nimport { z } from \"zod\";\n\ntype PostType = {\n    userId: number;\n    id: number;\n    title: string;\n    body: string;\n};\n\nexport const postRouter = t.router({\n    // Define a procedure that\n    // ...doesn't require an input and provides a query\n    // ...as `post.allPosts.useQuery()`\n    allPosts: t.procedure.query(async () =\u003e {\n        const allPosts = await fetch(\n            \"https://jsonplaceholder.typicode.com/posts\"\n        ).then((response) =\u003e response.json());\n        return { posts: posts as Array\u003cPostType\u003e };\n    }),\n    // Define a procedure that\n    // ...takes an id and provides a query\n    // ...as `post.postById.useQuery({ id })`\n    postById: t.procedure\n        .input(z.object({ id: z.string() }))\n        .query(async ({ input }) =\u003e {\n            const post = await fetch(\n                `https://jsonplaceholder.typicode.com/posts/${input.id}`\n            ).then((response) =\u003e response.json());\n            return { post: post as PostType };\n        }),\n});\n```\n\n## How can we use procedures?\n\n[Procedures](https://trpc.io/docs/v10/quickstart#add-a-query-procedure) are able to resolve any custom function to process a validated `{ input }`. Just to name a few examples: you could make use of an ORM like [Prisma](https://www.prisma.io/), a Baas like [Supabase](https://supabase.com), or a headless CMS like [Sanity](https://www.sanity.io/) to process your data with the benefits of fully typesafe APIs.\n\n## What about Mutations?\n\n[Mutations](https://tanstack.com/query/v4/docs/guides/mutations) are typically used to create/update/delete data. Let's take a look on how we could create a mutation procedure using our Post sub-router:\n\n```typescript\nimport { t } from \"../trpc\";\nimport { z } from \"zod\";\n\ntype PostType = {\n    userId: number;\n    id: number;\n    title: string;\n    body: string;\n};\n\nexport const postRouter = t.router({\n    // Define a procedure that\n    // ...takes an id and executes a mutation\n    // ...as `post.createPost.useMutation()`\n    // ...from `*.mutate({ input })`\n    createPost: t.procedure\n        .input(\n            z.object({\n                title: z.string().min(1),\n                body: z.string().min(1),\n                userId: z.number(),\n            })\n        )\n        .mutation(async ({ input }) =\u003e {\n            const post = await fetch(\n                \"https://jsonplaceholder.typicode.com/posts\",\n                { method: \"POST\", body: JSON.stringify(input) }\n            ).then((response) =\u003e response.json());\n            return { response: post as PostType };\n        }),\n});\n```\n\nHere is how we could execute that mutation from the client:\n\n```tsx\nimport { FormEvent, ChangeEvent, useState } from \"react\";\nimport { trpc as t } from \"../../utils/trpc\";\n\nexport default function NewPost() {\n    // User is fixed for simplicity\n    const initValue = { title: \"\", body: \"\", userId: 1 };\n    const [form, setForm] = useState(initValue);\n    const mutation = t.post.createPost.useMutation();\n\n    function handleChange(\n        e: ChangeEvent\u003cHTMLInputElement | HTMLTextAreaElement\u003e\n    ) {\n        setForm({ ...form, [e.target.id]: e.target.value });\n    }\n\n    function handleSubmit(e: FormEvent\u003cHTMLFormElement\u003e) {\n        e.preventDefault();\n        mutation.mutate(form);\n        setForm(initValue);\n    }\n\n    return (\n        \u003cform onSubmit={handleSubmit}\u003e\n            \u003clabel htmlFor=\"title\"\u003eTitle\u003c/label\u003e\n            \u003cinput\n                type=\"text\"\n                id=\"title\"\n                name=\"title\"\n                value={form.title}\n                onChange={handleChange}\n            /\u003e\n            \u003clabel htmlFor=\"body\"\u003eBody\u003c/label\u003e\n            \u003ctextarea\n                id=\"body\"\n                name=\"body\"\n                value={form.body}\n                onChange={handleChange}\n            /\u003e\n            \u003cbutton type=\"submit\"\u003eSubmit\u003c/button\u003e\n        \u003c/form\u003e\n    );\n}\n```\n\n\u003e Mutations are as simple to do as queries, they're actually the same underneath, but are just exposed differently as syntactic sugar and produce a HTTP POST rather than a GET request.\n\n## So WIFY by using tRPC?\n\nWhat's in it for you using tRPC? We barely scratched the surface, here are a list of features:\n\n-   Full static typesafety with autocompletion on the client, inputs, outputs and errors.\n-   No code generation, run-time bloat, or build pipeline.\n-   Zero dependencies and a tiny client-side footprint.\n-   All requests are able to be batched automatically into one.\n-   Framework agnostic and compatible with all JavaScript frameworks and runtimes.\n\nFeel free to explore this example using the resources below:\n\n[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/ekqt/trpc-basic-starter?file=src/pages/index.tsx\u0026file=src/server/routers/user.ts\u0026view=editor)\n\nOriginally published: [tRPC: Understanding Typesafety](https://www.webscope.io/blog/trpc-understanding-typesafety)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamhectorsosa%2Ftrpc-basic-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fiamhectorsosa%2Ftrpc-basic-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fiamhectorsosa%2Ftrpc-basic-starter/lists"}