---
title: "tRPC: End-to-End Type-Safe APIs in TypeScript Without Codegen"
lang: "en"
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/post/trpc-end-to-end-typesafe-apis
---

![Blog post image for tRPC: End-to-End Type-Safe APIs in TypeScript Without Codegen - How tRPC gives full-stack TypeScript teams end-to-end type safety with no code generation: routers, Zod validation, React Query, auth middleware, the v11 features (FormData, SSE, streaming), and when to pick it over REST or GraphQL.](/_astro/hero.DhkOXI-y_IHhDg.webp)

[Home](/)›[Blog](/blog)›[All Categories](/blog/categories)›[Web Development](/blog/categories/web-development)

Blog

[Prev in Web DevelopmentRESTful API vs. GraphQL: Which API is the Right Choice for Your Project?](/blog/post/restful-api-vs-graphql-which-api-is-the-right-choice-for-your-project)

[Web Development](/blog/categories/web-development)[Backend Engineering](/blog/categories/backend-engineering)

# tRPC: End-to-End Type-Safe APIs in TypeScript Without Codegen

[Mohammad Abu Mattar](/authors/mohammad-abu-mattar)Published: 20 Jul 202608 Mins read12 Mins listen

[Markdown for AI(opens in a new tab)](/post/trpc-end-to-end-typesafe-apis/index.md "Open the plain-Markdown version of this page, for pasting into an AI tool")

TL;DR

How tRPC gives full-stack TypeScript teams end-to-end type safety with no code generation: routers, Zod validation, React Query, auth middleware, the v11 features (FormData, SSE, streaming), and when to pick it over REST or GraphQL.

Series

[APIs & Advanced Architecture](/series/apis--advanced-architecture)4/4

[PreviousThe Real Talk on Microservices vs. Monoliths](/blog/post/0076-the-dark-side-of-microservices-when-to-avoid-them)

All posts in this series (4)

Blog4

1.  [RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?](/blog/post/restful-api-vs-graphql-which-api-is-the-right-choice-for-your-project)
2.  [REST API vs RESTful API: Architecture and Constraints Explained](/blog/post/rest-api-vs-restful-api-architecture-and-constraints-explained)
3.  [The Real Talk on Microservices vs. Monoliths](/blog/post/0076-the-dark-side-of-microservices-when-to-avoid-them)
4.  [tRPC: End-to-End Type-Safe APIs in TypeScript Without CodegenYou are here](/blog/post/trpc-end-to-end-typesafe-apis)

### tRPC: End-to-End Type-Safe APIs in TypeScript Without Codegen

Contents

[What tRPC is and why teams keep reaching for it](#what-trpc-is-and-why-teams-keep-reaching-for-it)[How the types actually flow](#how-the-types-actually-flow)[How tRPC differs from REST and GraphQL](#how-trpc-differs-from-rest-and-graphql)[Setting up a tRPC server](#setting-up-a-trpc-server)[Routers, procedures, and input validation with Zod](#routers-procedures-and-input-validation-with-zod)[The request lifecycle](#the-request-lifecycle)[Integrating with React Query on the frontend](#integrating-with-react-query-on-the-frontend)[Authentication and middleware](#authentication-and-middleware)[What is new in v11](#what-is-new-in-v11)[When to choose tRPC, GraphQL, or REST](#when-to-choose-trpc-graphql-or-rest)[Frequently Asked Questions](#frequently-asked-questions)[References](#references)

The line between the frontend and the backend has always been a place where types go to die. In a normal REST setup you define the shape of a request and a response on the server, then you write those same shapes again on the client. Nothing keeps the two copies honest. Someone renames a field in the database, the server response changes, and the client keeps trusting the old shape until a runtime error gives it away in production.

Code generation and OpenAPI specs try to close that gap, but they cost you a build step and a pile of config to maintain. tRPC takes a different route: if both sides are TypeScript, they can share the same type directly, so the client and server never drift. No generated code, no schema files, no extra pipeline.

Worth knowing

This post targets full-stack TypeScript teams, usually in a monorepo with Next.js or a Node backend. If your API also serves a Swift app or a Python service, read the last section first, because tRPC is probably the wrong tool for that boundary.

## [What tRPC is and why teams keep reaching for it](#what-trpc-is-and-why-teams-keep-reaching-for-it)

tRPC (TypeScript Remote Procedure Call) is a small framework for building type-safe APIs. The idea is simple: if the server and the client both run TypeScript, they should share one source of truth for their data contracts instead of maintaining two.

It is widely used, with tens of thousands of GitHub stars and millions of weekly downloads, and the reason is that it deletes the translation layer most APIs carry. You do not define HTTP endpoints, schema files, and resolvers. You define backend procedures, which are just functions that validate their input and return data. The frontend then imports the _type_ of those procedures, not the runtime code, and gets autocomplete, strict checking, and an instant compiler error the moment the backend changes.

In a monorepo that feedback loop is immediate. Rename a field on the server and the client component turns red before you even save the file. That is the whole pitch: the network boundary stops being a place where you guess.

## [How the types actually flow](#how-the-types-actually-flow)

The part that surprises people is that there is no magic and no generated artifact. The server exports a single type built from its router, and the client imports it. The TypeScript compiler does the rest.

The server exports \`type AppRouter = typeof appRouter\`. The client imports that type only (compile time, no codegen) and still talks to the server over normal batched HTTP at runtime.

A typical monorepo keeps the router definitions in their own package so server-only code never leaks into the client bundle. The client depends on that package for its _types_, not its runtime.

-   Directorymy-app/
    
    -   Directoryapps/
        
        -   Directoryweb/ (Next.js or React frontend)
            
            -   …
            
        
    -   Directorypackages/
        
        -   Directoryapi/
            
            -   Directorysrc/
                
                -   init.ts (tRPC instance + context)
                -   Directoryrouters/
                    
                    -   \_app.ts (root router, exports AppRouter type)
                    -   user.ts
                    
                -   server.ts (HTTP adapter)
                
            
        
    

## [How tRPC differs from REST and GraphQL](#how-trpc-differs-from-rest-and-graphql)

REST, GraphQL, and tRPC each treat type safety differently, and the differences explain when each one fits.

Feature

tRPC

GraphQL

REST

Protocol

RPC over HTTP

Query language over HTTP

HTTP verbs and resources

Schema format

TypeScript (source of truth)

SDL (`.graphql` files)

OpenAPI or none

Type safety

End-to-end, automatic

Generated via codegen

Generated or manual

Client coupling

Tight (shares types)

Loose (schema contract)

Loose (HTTP contract)

Multi-client support

TypeScript only

Excellent

Excellent

REST usually gets its types from a spec. You produce an OpenAPI document from the server and run something like `openapi-typescript` to generate client types. It works, but the safety lives or dies by that build step, and a skipped or broken generation quietly leaves you with a loosely typed boundary again.

GraphQL leans on a strongly typed schema (SDL) and a codegen step (`graphql-codegen`) to turn that schema into client types. The safety is real, but so is the boilerplate: schema, resolvers, and queries are all written separately.

tRPC skips the schema and the codegen entirely. The server exports a TypeScript type, the client imports it, and the compiler infers everything. There is no step to keep the two sides in sync because they are literally reading the same type.

## [Setting up a tRPC server](#setting-up-a-trpc-server)

A server needs three things: a context, an initialized tRPC instance, and an HTTP adapter. tRPC ships official adapters for Express, Fastify, the Fetch API (Next.js App Router, edge runtimes), and the plain Node HTTP server.

1.  Create the context factory. It runs per request and is where you read the auth token and attach shared resources like a database client.
2.  Initialize tRPC with that context type, and export the reusable `router` and `publicProcedure` builders.
3.  Mount the root router on an HTTP adapter for your framework.

packages/api/src/init.ts

```
1import {db} from './db';2import {initTRPC, TRPCError} from '@trpc/server';3import type {CreateExpressContextOptions} from '@trpc/server/adapters/express';4
5// Runs on every request. Whatever you return here is the `ctx` in procedures.6export function createContext({req}: CreateExpressContextOptions) {7  const token = req.headers.authorization?.replace('Bearer ', '') ?? null;8  return {token, db};9}10
11export type Context = Awaited<ReturnType<typeof createContext>>;12
13const t = initTRPC.context<Context>().create();14
15export const router = t.router;16export const publicProcedure = t.procedure;
```

The adapter is the only part that changes between frameworks. Everything above stays identical.

-   [Express](#tab-panel-53)
-   [Next.js (App Router)](#tab-panel-54)

packages/api/src/server.ts

```
1import {createContext} from './init';2import {appRouter} from './routers/_app';3import {createExpressMiddleware} from '@trpc/server/adapters/express';4import express from 'express';5
6const app = express();7
8app.use('/trpc', createExpressMiddleware({router: appRouter, createContext}));9
10app.listen(4000, () => console.log('tRPC on http://localhost:4000/trpc'));
```

apps/web/src/app/api/trpc/\[trpc\]/route.ts

```
1import {createContext} from '@repo/api/init';2import {appRouter} from '@repo/api/routers/_app';3import {fetchRequestHandler} from '@trpc/server/adapters/fetch';4
5const handler = (req: Request) =>6  fetchRequestHandler({7    endpoint: '/api/trpc',8    req,9    router: appRouter,10    createContext,11  });12
13export {handler as GET, handler as POST};
```

## [Routers, procedures, and input validation with Zod](#routers-procedures-and-input-validation-with-zod)

A procedure is one endpoint. It is either a `query` (reads) or a `mutation` (writes), and procedures are grouped into routers. Input validation is not optional here, and tRPC integrates natively with Zod, which is the community default because its inference lines up perfectly with TypeScript.

When you pass a Zod schema to `.input()`, tRPC uses it twice. At runtime the server parses the request against the schema and throws if the data is malformed. At compile time it infers the type from the same schema and enforces it on the client, so the client cannot even send the wrong shape.

packages/api/src/routers/user.ts

```
1import {router, publicProcedure} from '../init';2import {z} from 'zod';3
4export const userRouter = router({5  // Query: fetching data6  getById: publicProcedure7    .input(z.object({id: z.string().uuid()}))8    .query(async ({input, ctx}) => {9      // `input.id` is a validated string by the time we get here.10      return ctx.db.user.findUnique(input.id);11    }),12
13  // Mutation: changing data14  create: publicProcedure15    .input(z.object({email: z.string().email(), name: z.string().min(2)}))16    .mutation(async ({input, ctx}) => {17      return ctx.db.user.create(input);18    }),19});
```

Individual routers merge into one root router, and that root router is the only thing the frontend needs a type for.

packages/api/src/routers/\_app.ts

```
1import {router} from '../init';2import {userRouter} from './user';3
4export const appRouter = router({5  user: userRouter,6});7
8// The single type the frontend imports.9export type AppRouter = typeof appRouter;
```

## [The request lifecycle](#the-request-lifecycle)

It helps to see what a single call actually does on the way through. The client batches the call, the adapter builds a context, middleware runs, Zod validates, and only then does your resolver see the data.

Auth and validation happen before your resolver runs. A failed check short-circuits with a typed TRPCError (401 or 400) instead of reaching your business logic.

## [Integrating with React Query on the frontend](#integrating-with-react-query-on-the-frontend)

tRPC has a first-class integration with TanStack React Query. Version 11 changed how it works. The old approach wrapped React Query in tRPC-specific hooks like `trpc.useQuery`. The new integration, in `@trpc/tanstack-react-query`, is query-native: it hands you the `queryOptions` and `mutationOptions` that TanStack Query already understands, so you call the standard `useQuery` and `useMutation` yourself. Less to learn, and it plays nicely with the React Compiler.

You set up a proxy once:

apps/web/src/trpc.ts

```
1import type {AppRouter} from '@repo/api/routers/_app';2import {QueryClient} from '@tanstack/react-query';3import {createTRPCClient, httpBatchLink} from '@trpc/client';4import {createTRPCOptionsProxy} from '@trpc/tanstack-react-query';5
6export const queryClient = new QueryClient();7
8const trpcClient = createTRPCClient<AppRouter>({9  links: [httpBatchLink({url: 'http://localhost:4000/trpc'})],10});11
12// Strongly-typed factory for query keys, query options, and mutation options.13export const trpc = createTRPCOptionsProxy<AppRouter>({14  client: trpcClient,15  queryClient,16});
```

Then you use plain TanStack Query hooks in components. The proxy supplies the typed keys, the URL, and the fetching logic.

apps/web/src/components/UserProfile.tsx

```
1'use client';2import {trpc, queryClient} from '../trpc';3import {useQuery, useMutation} from '@tanstack/react-query';4
5export function UserProfile({userId}: {userId: string}) {6  const {data, isLoading, error} = useQuery(7    trpc.user.getById.queryOptions({id: userId}),8  );9
10  const {mutate, isPending} = useMutation(11    trpc.user.create.mutationOptions({12      onSuccess: () =>13        queryClient.invalidateQueries({14          queryKey: trpc.user.getById.queryKey(),15        }),16    }),17  );18
19  if (isLoading) return <div>Loading...</div>;20  if (error) return <div>Error fetching user.</div>;21
22  return (23    <div>24      <h1>{data?.name}</h1>25      <button26        disabled={isPending}27        onClick={() => mutate({email: 'test@example.com', name: 'Alice'})}28      >29        {isPending ? 'Saving...' : 'Create User'}30      </button>31    </div>32  );33}
```

## [Authentication and middleware](#authentication-and-middleware)

Middleware intercepts a request before it reaches your resolver, which makes it the right home for logging, timing, and authorization. The pattern for auth is to check the context and then extend it with `opts.next()`, so downstream procedures receive a guaranteed, non-null user.

packages/api/src/middleware.ts

```
1import {verifyToken} from './auth';2import {t} from './init';3import {TRPCError} from '@trpc/server';4
5const isAuthed = t.middleware(async ({ctx, next}) => {6  if (!ctx.token) {7    throw new TRPCError({code: 'UNAUTHORIZED', message: 'Missing token'});8  }9
10  const user = await verifyToken(ctx.token);11  if (!user) throw new TRPCError({code: 'UNAUTHORIZED'});12
13  // Extend the context. From here down, `ctx.user` is guaranteed to exist.14  return next({ctx: {user}});15});16
17export const protectedProcedure = t.procedure.use(isAuthed);
```

Because the middleware narrows the type, any procedure built on `protectedProcedure` gets a non-nullable `ctx.user` with no extra null checks.

packages/api/src/routers/admin.ts

```
1import {router, protectedProcedure} from '../init';2
3export const adminRouter = router({4  getDashboardStats: protectedProcedure.query(({ctx}) => {5    // TypeScript knows `ctx.user` is defined here.6    console.log(`Stats for admin ${ctx.user.id}`);7    return {activeUsers: 100, revenue: 5000};8  }),9});
```

## [What is new in v11](#what-is-new-in-v11)

tRPC v11 (March 2025) pushed past plain request/response, and these are the additions most teams will actually use.

**FormData and file uploads.** Procedures can now accept non-JSON content, including `FormData` and binary types like `Blob`, `File`, and `Uint8Array`. That means real file uploads without a separate REST endpoint.

packages/api/src/routers/upload.ts

```
1import {router, publicProcedure} from '../init';2import {TRPCError} from '@trpc/server';3import {z} from 'zod';4
5export const uploadRouter = router({6  avatar: publicProcedure7    .input(z.instanceof(FormData))8    .mutation(async ({input}) => {9      const file = input.get('file');10      if (!(file instanceof File)) {11        throw new TRPCError({code: 'BAD_REQUEST', message: 'No file'});12      }13      // stream `file` to storage here...14      return {name: file.name, size: file.size};15    }),16});
```

**Subscriptions over Server-Sent Events.** Real-time updates no longer force you onto WebSockets. Subscriptions are written as async generators, so you `yield` values over time and clean up naturally when the client disconnects.

packages/api/src/routers/messages.ts

```
1import {router, publicProcedure} from '../init';2import {z} from 'zod';3
4export const messageRouter = router({5  onMessage: publicProcedure6    .input(z.object({roomId: z.string()}))7    .subscription(async function* ({input, ctx, signal}) {8      // `ctx.bus` is any async iterable event source.9      for await (const msg of ctx.bus.subscribe(input.roomId, {signal})) {10        yield msg; // pushed to the client over SSE11      }12    }),13});
```

**Streaming queries and mutations.** With `httpBatchStreamLink`, a resolver can return a generator and stream results over plain HTTP, no WebSocket required. Pair it with `httpSubscriptionLink` for the SSE subscriptions above.

apps/web/src/trpc.ts (links)

```
1import {2  createTRPCClient,3  httpBatchStreamLink,4  httpSubscriptionLink,5  splitLink,6} from '@trpc/client';7
8const trpcClient = createTRPCClient<AppRouter>({9  links: [10    splitLink({11      condition: (op) => op.type === 'subscription',12      true: httpSubscriptionLink({url: '/trpc'}),13      false: httpBatchStreamLink({url: '/trpc'}),14    }),15  ],16});
```

Subscriptions also gained output validators in v11, so the values you stream are type-checked the same way inputs are.

## [When to choose tRPC, GraphQL, or REST](#when-to-choose-trpc-graphql-or-rest)

tRPC is not a universal answer. The call comes down to who consumes the API and what language they speak.

Reach for tRPC when you own both ends and they are both TypeScript, usually in a monorepo. That covers internal dashboards, SaaS products on Next.js or React, and anything where developer speed matters more than a portable contract. Dropping schema files and codegen is a real velocity win for a TypeScript-only team.

Stay on REST for public APIs. If your backend has to serve a Swift app, Python microservices, or partner integrations, REST gives you the HTTP semantics, caching, and tooling everyone already supports. Adapters like `trpc-openapi` can expose REST endpoints from tRPC procedures, but treating REST as an afterthought rarely ends well for heavy public use.

Pick GraphQL for federated microservices or complex clients that need fine-grained control over payloads to avoid over-fetching. Its schema contracts let independent teams work across a big graph and aggregate many systems into one API, which is exactly the problem tRPC does not try to solve.

## [Frequently Asked Questions](#frequently-asked-questions)

No. It is a better fit for one specific case: a full-stack TypeScript codebase where you control the client and the server. Public APIs and non-TypeScript consumers are still REST or GraphQL territory.

Correct. The server exports `type AppRouter = typeof appRouter` and the client imports that type. TypeScript infers everything, so there is no schema file and no generation step to run or keep in sync.

tRPC works with Zod, Yup, Valibot, and others, but Zod is standard because its inference maps cleanly onto TypeScript. One schema gives you runtime validation and the compile-time input type at once.

Not strictly, but it is the smoothest setup because the client imports the server’s type directly. You can also publish the API package privately and consume its types from a separate repo, at the cost of a versioning step.

Yes, as of v11. Subscriptions run over Server-Sent Events using async generators, and procedures accept `FormData` and binary types for uploads. You no longer need a side channel for either.

You throw a `TRPCError` with a code like `UNAUTHORIZED` or `BAD_REQUEST`. tRPC maps it to the right HTTP status and delivers a typed error to the client, where React Query surfaces it as the `error` on the hook.

## [References](#references)

-   [Announcing tRPC v11](https://trpc.io/blog/announcing-trpc-v11)
-   [Migrate from v10 to v11](https://trpc.io/docs/migrate-from-v10-to-v11)
-   [Introducing the new TanStack React Query integration](https://trpc.io/blog/introducing-tanstack-react-query-client)
-   [TanStack React Query usage](https://trpc.io/docs/client/tanstack-react-query/usage)
-   [tRPC Express adapter](https://trpc.io/docs/server/adapters/express)
-   [tRPC Fetch adapter (Next.js / edge)](https://trpc.io/docs/server/adapters/fetch)
-   [Middlewares and context](https://trpc.io/docs/server/middlewares)
-   [Subscriptions](https://trpc.io/docs/server/subscriptions)
-   [Zod](https://zod.dev/)

Was this useful?

## Tags

[#TRPC](/blog/tags/trpc)[#TypeScript](/blog/tags/typescript)[#Node.js](/blog/tags/nodejs)[#REST API](/blog/tags/rest-api)[#Type Safety](/blog/tags/type-safety)[#React Query](/blog/tags/react-query)[#Zod](/blog/tags/zod)

## Share

[Facebook](https://facebook.com/sharer/sharer.php?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis "Share on Facebook")[Twitter](https://twitter.com/intent/tweet/?text=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis "Share on Twitter")[LinkedIn](https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis&title=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen&summary=How%20tRPC%20gives%20full-stack%20TypeScript%20teams%20end-to-end%20type%20safety%20with%20no%20code%20generation%3A%20routers%2C%20Zod%20validation%2C%20React%20Query%2C%20auth%20middleware%2C%20the%20v11%20features%20\(FormData%2C%20SSE%2C%20streaming\)%2C%20and%20when%20to%20pick%20it%20over%20REST%20or%20GraphQL.&source=https://mkabumattar.com "Share on LinkedIn")[WhatsApp](https://wa.me/?text=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen%20https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis "Share on WhatsApp")[Telegram](https://t.me/share/url?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis&text=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen "Share on Telegram")[Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis&title=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen "Share on Reddit")[Hacker News](http://news.ycombinator.com/submitlink?u=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis&t=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen "Share on Hacker News")[Pinterest](https://pinterest.com/pin/create/button/?url=https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis&media=&description=How%20tRPC%20gives%20full-stack%20TypeScript%20teams%20end-to-end%20type%20safety%20with%20no%20code%20generation%3A%20routers%2C%20Zod%20validation%2C%20React%20Query%2C%20auth%20middleware%2C%20the%20v11%20features%20\(FormData%2C%20SSE%2C%20streaming\)%2C%20and%20when%20to%20pick%20it%20over%20REST%20or%20GraphQL. "Share on Pinterest")[Email](<mailto:?subject=tRPC%3A%20End-to-End%20Type-Safe%20APIs%20in%20TypeScript%20Without%20Codegen&body=Check out this article: https%3A%2F%2Fmkabumattar.com%2Fblog%2Fpost%2Ftrpc-end-to-end-typesafe-apis>)

## Comments

## You might also enjoy

More posts on similar topics

[![RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?](/_astro/hero.Do4aqVvj_Z15DwQr.webp)](/blog/post/restful-api-vs-graphql-which-api-is-the-right-choice-for-your-project)

## [RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?](/blog/post/restful-api-vs-graphql-which-api-is-the-right-choice-for-your-project)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [API Design](/blog/categories/api-design)
-   [Web Development](/blog/categories/web-development)
-   [Backend Architecture](/blog/categories/backend-architecture)
-   [Software Engineering](/blog/categories/software-engineering)

TL;DR When deciding between RESTful and GraphQL APIs for a data analysis and display application, weigh the advantages and disadvantages of each. RESTful APIs have been around for a long time and

[#RESTful API](/blog/tags/restful-api)[#GraphQL](/blog/tags/graphql)[#API Comparison](/blog/tags/api-comparison)+6 tags

[read more](/blog/post/restful-api-vs-graphql-which-api-is-the-right-choice-for-your-project)

[![REST API vs RESTful API: Architecture and Constraints Explained](/_astro/hero.D7ffsaFk_ZsjslT.webp)](/blog/post/rest-api-vs-restful-api-architecture-and-constraints-explained)

## [REST API vs RESTful API: Architecture and Constraints Explained](/blog/post/rest-api-vs-restful-api-architecture-and-constraints-explained)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [API Development](/blog/categories/api-development)
-   [Web Architecture](/blog/categories/web-architecture)
-   [Software Engineering](/blog/categories/software-engineering)
-   [Backend Development](/blog/categories/backend-development)

Introduction REST API and RESTful API get used interchangeably, but they aren't quite the same thing. This post covers the difference, REST's constraints, and what they mean for how you design an

[#REST API](/blog/tags/rest-api)[#RESTful API](/blog/tags/restful-api)[#API Design Principles](/blog/tags/api-design-principles)+6 tags

[read more](/blog/post/rest-api-vs-restful-api-architecture-and-constraints-explained)

[![The Real Talk on Microservices vs. Monoliths](/_astro/hero.CCnE1S4X_Z1X4cS2.webp)](/blog/post/0076-the-dark-side-of-microservices-when-to-avoid-them)

## [The Real Talk on Microservices vs. Monoliths](/blog/post/0076-the-dark-side-of-microservices-when-to-avoid-them)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Software Architecture](/blog/categories/software-architecture)
-   [Microservices](/blog/categories/microservices)
-   [Monoliths](/blog/categories/monoliths)
-   [System Design](/blog/categories/system-design)

The tricky side of tiny boxes: when smaller isn't always better So, microservices, right? They're all the rage in the software world these days. Everyone's buzzing about how they make things super

[#Microservices](/blog/tags/microservices)[#Monoliths](/blog/tags/monoliths)[#Software Architecture](/blog/tags/software-architecture)+7 tags

[read more](/blog/post/0076-the-dark-side-of-microservices-when-to-avoid-them)

[![Get Started with Building ReactJS and Docker: A Complete Guide](/_astro/hero.CDA9gINC_5FejQ.webp)](/blog/post/get-started-with-building-reactjs-and-docker-a-complete-guide)

## [Get Started with Building ReactJS and Docker: A Complete Guide](/blog/post/get-started-with-building-reactjs-and-docker-a-complete-guide)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [ReactJS](/blog/categories/reactjs)
-   [Docker](/blog/categories/docker)
-   [DevOps](/blog/categories/devops)
-   [Containerization](/blog/categories/containerization)
-   [Web Development](/blog/categories/web-development)

Introduction Docker is a powerful tool that allows developers to create, deploy, and run applications in a portable and scalable way. It uses containerization to encapsulate all the dependencies a

[#ReactJS](/blog/tags/reactjs)[#Docker](/blog/tags/docker)[#Dockerfile](/blog/tags/dockerfile)+5 tags

[read more](/blog/post/get-started-with-building-reactjs-and-docker-a-complete-guide)

[![Run TypeScript Without Compiling](/_astro/hero.EI1J4T1U_ZTF3Kf.webp)](/blog/post/run-typescript-without-compiling)

## [Run TypeScript Without Compiling](/blog/post/run-typescript-without-compiling)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [TypeScript](/blog/categories/typescript)
-   [Node.js](/blog/categories/nodejs)
-   [JavaScript](/blog/categories/javascript)
-   [Development Tools](/blog/categories/development-tools)

Introduction In this post, I will show you how to run TypeScript without compiling it to JavaScript first. This is useful for debugging and testing. Set up a TypeScript project Step 1: cr

[#TypeScript](/blog/tags/typescript)[#Node.js](/blog/tags/nodejs)[#Ts node](/blog/tags/ts-node)+6 tags

[read more](/blog/post/run-typescript-without-compiling)

[![Setting up Node.js, Express, Prettier, ESLint, and Husky application with Babel and TypeScript - Part 1](/_astro/hero.DKzl3k6w_w3X8j.webp)](/blog/post/setting-up-node-js-express-prettier-eslint-and-husky-application-with-babel-and-typescript-part-1)

## [Setting up Node.js, Express, Prettier, ESLint, and Husky application with Babel and TypeScript - Part 1](/blog/post/setting-up-node-js-express-prettier-eslint-and-husky-application-with-babel-and-typescript-part-1)

-   [Mohammad Abu Mattar](/authors/mohammad-abu-mattar)
-   [Backend Development](/blog/categories/backend-development)
-   [Node.js](/blog/categories/nodejs)
-   [TypeScript](/blog/categories/typescript)
-   [Development Setup](/blog/categories/development-setup)
-   [JavaScript Tooling](/blog/categories/javascript-tooling)

Introduction All code from this tutorial as a complete package is available in this repos

[#Node.js](/blog/tags/nodejs)[#Express.js](/blog/tags/expressjs)[#TypeScript](/blog/tags/typescript)+9 tags

[read more](/blog/post/setting-up-node-js-express-prettier-eslint-and-husky-application-with-babel-and-typescript-part-1)

6 related posts
