惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
LangChain Blog
雷峰网
雷峰网
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
T
Tailwind CSS Blog
V
Visual Studio Blog
博客园_首页
Apple Machine Learning Research
Apple Machine Learning Research
Last Week in AI
Last Week in AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
有赞技术团队
有赞技术团队
J
Java Code Geeks
酷 壳 – CoolShell
酷 壳 – CoolShell
大猫的无限游戏
大猫的无限游戏
Engineering at Meta
Engineering at Meta
B
Blog
Recent Announcements
Recent Announcements
C
Check Point Blog
MongoDB | Blog
MongoDB | Blog
GbyAI
GbyAI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
F
Full Disclosure
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
I
InfoQ
云风的 BLOG
云风的 BLOG
量子位
D
Docker
D
DataBreaches.Net
Vercel News
Vercel News
Blog — PlanetScale
Blog — PlanetScale
宝玉的分享
宝玉的分享
V
V2EX
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Y
Y Combinator Blog
美团技术团队
小众软件
小众软件
阮一峰的网络日志
阮一峰的网络日志
博客园 - 聂微东
B
Blog RSS Feed
MyScale Blog
MyScale Blog
月光博客
月光博客
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
aimingoo的专栏
aimingoo的专栏
M
MIT News - Artificial intelligence
U
Unit 42
Google DeepMind News
Google DeepMind News

Deno

Deno 2.8 | Deno Claw Patrol: an open-source security firewall for agents | Deno Fresh 2.3: Zero JS by default, View Transitions, and Temporal support | Deno Deno 2.7: Temporal API, Windows ARM, and npm overrides | Deno Build a dinosaur runner game with Deno, pt. 6 | Deno Build a dinosaur runner game with Deno, pt. 5 | Deno Deno Deploy is Generally Available | Deno Introducing Deno Sandbox | Deno Build a dinosaur runner game with Deno, pt. 4 | Deno Build a dinosaur runner game with Deno, pt. 3 | Deno Build a dinosaur runner game with Deno, pt. 2 | Deno React / Next.js Denial-of-Service Vulnerability: Deno Deploy users protected | Deno Deno 2.6: dx is the new npx | Deno Build a dinosaur runner game with Deno, pt. 1 | Deno React Server Functions / Next.js Vulnerability: Deno Deploy users protected | Deno My highlights from the new Deno Deploy | Deno Deno's Other Open Source Projects | Deno How Deno protects against npm exploits | Deno Help Us Raise $200k to Free JavaScript from Oracle | Deno Deno 2.5: Permissions in the config file | Deno Fresh 2.0 Graduates to Beta, Adds Vite Support | Deno Deno 2.4: deno bundle is back | Deno JavaScript™ Trademark Update | Deno What's coming to JavaScript | Deno A brief history of JavaScript | Deno Reports of Deno's Demise Have Been Greatly Exaggerated | Deno An Update on Fresh | Deno How Plaid migrated 100 services to a new database platform 5x faster with Deno | Deno Deno 2.3: Improved deno compile, local npm packages, and more | Deno Add JSR packages with pnpm and Yarn | Deno Zero-config Debugging with Deno and OpenTelemetry | Deno Exploring Art with TypeScript, Jupyter, Polars, and Observable Plot | Deno Deno v Oracle Update 3: Fighting the JavaScript Trademark | Deno Build a custom RAG AI agent in TypeScript and Jupyter | Deno How to get deep traces in your Node.js backend with OTel and Deno | Deno toranoana.deno #20 登録受付中(2025年3月14日) | Deno Node just added TypeScript support. What does that mean for Deno? | Deno The Dino 🦕, the Llama 🦙, and the Whale 🐋 | Deno Publish a lint rule, get a prize | Deno Deno 2.2: OpenTelemetry, Lint Plugins, node:sqlite | Deno If you're not using npm specifiers, you're doing it wrong | Deno How Deno's documentation is evolving | Deno Oracle justified its JavaScript trademark with Node.js—now it wants that ignored | Deno Introducing the JSR open governance board | Deno Intro to Wasm in Deno | Deno Announcing OpenAI on JSR | Deno Deno in 2024 | Deno Goodbye WinterCG, welcome WinterTC | Deno Build a SolidJS app with Deno | Deno Run your Next.js SSR app on Deno Deploy | Deno Solve Advent of Code 2024 with Deno and Win Prizes! | Deno Deno v. Oracle: Canceling the JavaScript Trademark | Deno Deno 2.1: Wasm Imports and other enhancements | Deno Self-contained Executable Programs with Deno Compile | Deno Build a Database App with Drizzle ORM and Deno | Deno Introducing your new JavaScript package manager: Deno | Deno Announcing Growthbook on JSR | Deno Build an Astro site with Deno | Deno How to convert CommonJS to ESM | Deno Announcing Deno 2 | Deno The Final Touches: What’s New In v2.0.0-rc.10 | Deno Announcing Stable V8 Bindings for Rust | Deno Deno 2.0 Release Candidate | Deno Secure, efficient private npm registries with Cloudsmith and Deno | Deno Painting the Plane as We Fly It: Designing JSR | Deno Introducing Web Cache API support on Deno Deploy | Deno Deno 1.46: The Last 1.x Release | Deno Protect your cloud spend with new Deno Deploy spend limits | Deno What we got wrong about HTTP imports | Deno Benchmarking AWS Lambda Cold Starts Across JavaScript Runtimes | Deno Announcing Supabase on JSR | Deno Deno 1.45: Workspace and Monorepo Support | Deno Introducing KV Backup for Deno Subhosting | Deno A Gentle Intro to TypeScript | Deno Announcing Hono on JSR | Deno How We Made the Deno Language Server Ten Times Faster | Deno How the Guardian uses Deno to audit accessibility and performance across their 2.7 million articles | Deno Introducing More Flexible Domain Association for Deno Subhosting | Deno The stabilization process of the Standard Library has begun | Deno Deno 1.44: Private npm registries, improved Node.js compat, and performance boosts | Deno How we built a secure, performant, multi-tenant cloud platform to run untrusted code | Deno The Deno Standard Library is now available on JSR | Deno How to document your JavaScript package | Deno Your Low Code Solution Needs an Escape Hatch | Deno Deno 1.43: Improved Language Server performance | Deno How Slack used Deno to save months of engineering effort in launching their new platform | Deno JSR Is Not Another Package Manager | Deno Announcing the Hookdeck SDK on JSR | Deno Announcing the Neon Serverless Driver on JSR | Deno An intro to TSConfig for JavaScript Developers | Deno How we built JSR | Deno How Netlify used Deno Subhosting to build a successful edge functions product | Deno Introducing Simpler Project Creation in Deno Deploy | Deno Deno 1.42: Better dependency management with JSR | Deno Introducing deployctl, the command line interface for Deno Deploy | Deno Introducing JSR - the JavaScript Registry | Deno How to add Monaco to a Next.js app and securely run untrusted user code | Deno Survey Results and Roadmap | Deno Deno 1.41: smaller deno compile binaries | Deno Webhooks suck, but here are alternatives | Deno
Build a Typesafe API with tRPC and Deno | Deno
Andy Jiang · 2024-11-19 · via Deno

Deno is an all-in-one, zero-config toolchain for writing JavaScript and TypeScript with natively supports Web Platform APIs, making it an ideal choice for quickly building backends and APIs. To make our API easier to maintain, we can use tRPC, a TypeScript RPC (Remote Procedure Call) framework that enables you to build fully type-safe APIs without schema declarations or code generation.

In this tutorial, we’ll build a simple type-safe API with tRPC and Deno that returns information about dinosaurs:

  • Set up tPRC
  • Set up the server
  • Set up the client
  • What’s next?

You can find all the code for this tutorial in this GitHub repo.

🚨️ Deno 2 is here. 🚨️

With backwards compatibility with Node/npm, built-in package management, all-in-one zero-config toolchain, and native TypeScript and web API support, writing JavaScript has never been simpler.

Set up tRPC

To get started with tRPC in Deno, we’ll need to install the required dependencies. Thanks to Deno’s npm compatibility, we can use the npm versions of tRPC packages along with Zod for input validation:

deno install npm:@trpc/server@next npm:@trpc/client@next npm:zod jsr:@std/path

This installs the most recent tRPC server and client packages, Zod for runtime type validation, and the Deno Standard Library’s path utility. These packages will allow us to build a type-safe API layer between our client and server code.

This will create a deno.json file in the project root to manage the npm and jsr dependencies:

{
  "imports": {
    "@std/path": "jsr:@std/path@^1.0.6",
    "@trpc/client": "npm:@trpc/client@^11.0.0-rc.593",
    "@trpc/server": "npm:@trpc/server@^11.0.0-rc.593",
    "zod": "npm:zod@^3.23.8"
  }
}

Set up the tRPC server

The first step in building our tRPC application is setting up the server. We’ll start by initializing tRPC and creating our base router and procedure builders. These will be the foundation for defining our API endpoints.

Create a server/trpc.ts file:



import { initTRPC } from "@trpc/server";



const t = initTRPC.create();



export const router = t.router;
export const publicProcedure = t.procedure;

This initializes tRPC and exports the router and procedure builders that we’ll use to define our API endpoints. The publicProcedure allows us to create endpoints that don’t require authentication.

Next, we’ll create a simple data layer to manage our dinosaur data. Create a server/db.ts file with the below:


import { join } from "@std/path";

type Dino = { name: string; description: string };

const dataPath = join("data", "data.json");

async function readData(): Promise<Dino[]> {
  const data = await Deno.readTextFile(dataPath);
  return JSON.parse(data);
}

async function writeData(dinos: Dino[]): Promise<void> {
  await Deno.writeTextFile(dataPath, JSON.stringify(dinos, null, 2));
}

export const db = {
  dino: {
    findMany: () => readData(),
    findByName: async (name: string) => {
      const dinos = await readData();
      return dinos.find((dino) => dino.name === name);
    },
    create: async (data: { name: string; description: string }) => {
      const dinos = await readData();
      const newDino = { ...data };
      dinos.push(newDino);
      await writeData(dinos);
      return newDino;
    },
  },
};

This creates a simple file-based database that reads and writes dinosaur data to a JSON file. In a production environment, you’d typically use a proper database, but this will work well for our demo.

⚠️️ In this tutorial, we hard code data and use a file-based database. However, you can connect to a variety of databases and use ORMs like Drizzle or Prisma.

Finally, we’ll need to provide the actual data. Let’s create a ./data.json file with some sample dinosaur data:


[
  {
    "name": "Aardonyx",
    "description": "An early stage in the evolution of sauropods."
  },
  {
    "name": "Abelisaurus",
    "description": "\"Abel's lizard\" has been reconstructed from a single skull."
  },
  {
    "name": "Abrictosaurus",
    "description": "An early relative of Heterodontosaurus."
  },
  {
    "name": "Abrosaurus",
    "description": "A close Asian relative of Camarasaurus."
  },
  ...
 ]

Now, we can create our main server file that defines our tRPC router and procedures. Create a server/index.ts file:



import { createHTTPServer } from "@trpc/server/adapters/standalone";
import { z } from "zod";
import { db } from "./db.ts";
import { publicProcedure, router } from "./trpc.ts";

const appRouter = router({
  dino: {
    list: publicProcedure.query(async () => {
      const dinos = await db.dino.findMany();
      return dinos;
    }),
    byName: publicProcedure.input(z.string()).query(async (opts) => {
      const { input } = opts;
      const dino = await db.dino.findByName(input);
      return dino;
    }),
    create: publicProcedure
      .input(z.object({ name: z.string(), description: z.string() }))
      .mutation(async (opts) => {
        const { input } = opts;
        const dino = await db.dino.create(input);
        return dino;
      }),
  },
  examples: {
    iterable: publicProcedure.query(async function* () {
      for (let i = 0; i < 3; i++) {
        await new Promise((resolve) => setTimeout(resolve, 500));
        yield i;
      }
    }),
  },
});


export type AppRouter = typeof appRouter;

const server = createHTTPServer({
  router: appRouter,
});

server.listen(3000);

This sets up three main endpoints:

  • dino.list: Returns all dinosaurs
  • dino.byName: Returns a specific dinosaur by name
  • dino.create: Creates a new dinosaur
  • examples.iterable: A demonstration of tRPC’s support for async iterables

The server is configured to listen on port 3000 and will handle all tRPC requests.

While you can run the server now, you won’t be able to access any of the routes and have it return data. Let’s fix that!

Set up the tRPC client

With our server ready, we can create a client that consumes our API with full type safety. Create a client/index.ts file:



import {
  createTRPCClient,
  splitLink,
  unstable_httpBatchStreamLink,
  unstable_httpSubscriptionLink,
} from "@trpc/client";

import type { AppRouter } from "../server/index.ts";


const trpc = createTRPCClient<AppRouter>({
  links: [
    splitLink({
      condition: (op) => op.type === "subscription",
      true: unstable_httpSubscriptionLink({
        url: "http://localhost:3000",
      }),
      false: unstable_httpBatchStreamLink({
        url: "http://localhost:3000",
      }),
    }),
  ],
});

const dinos = await trpc.dino.list.query();
console.log("Dinos:", dinos);

const createdDino = await trpc.dino.create.mutate({
  name: "Denosaur",
  description:
    "A dinosaur that lives in the deno ecosystem. Eats Nodes for breakfast.",
});
console.log("Created dino:", createdDino);

const dino = await trpc.dino.byName.query("Denosaur");
console.log("Denosaur:", dino);

const iterable = await trpc.examples.iterable.query();

for await (const i of iterable) {
  console.log("Iterable:", i);
}

This client code demonstrates several key features of tRPC:

  1. Type inference from the server router. The client automatically inherits all type definitions from the server through the AppRouter type import. This means you get complete type support and compile-time type checking for all your API calls. If you modify a procedure on the server, TypeScript will immediately flag any incompatible client usage.
  2. Making queries and mutations. The example demonstrates two types of API calls: Queries (list and byName) used for fetching data without side effects, and mutations (create) used for operations that modify server-side state. The client automatically knows the input and output types for each procedure, providing type safety throughout the entire request cycle.
  3. Working with async iterables. The examples.iterable demonstrates tRPC’s support for streaming data using async iterables. This feature is particularly useful for real-time updates or processing large datasets in chunks.

Now, let’s start our server to see it in action. In our deno.json config file, let’s create a new property tasks with the following commands:

{
  "tasks": {
    "start": "deno -A server/index.ts",
    "client": "deno -A client/index.ts"
  }
  
}

We can list our available tasks with deno task:

deno task
Available tasks:
- start
    deno -A server/index.ts
- client
    deno -A client/index.ts

Now, we can start the server with deno task start. After that’s running, we can run the client with deno task client. You should see an output like this:

deno task client
Dinos: [
  {
    name: "Aardonyx",
    description: "An early stage in the evolution of sauropods."
  },
  {
    name: "Abelisaurus",
    description: "Abel's lizard has been reconstructed from a single skull."
  },
  {
    name: "Abrictosaurus",
    description: "An early relative of Heterodontosaurus."
  },
  ...
]
Created dino: {
  name: "Denosaur",
  description: "A dinosaur that lives in the deno ecosystem. Eats Nodes for breakfast."
}
Denosaur: {
  name: "Denosaur",
  description: "A dinosaur that lives in the deno ecosystem. Eats Nodes for breakfast."
}
Iterable: 0
Iterable: 1
Iterable: 2

Success! Running the ./client/index.ts shows how to create a tRPC client and use its JavaScript API to interact with the database. But how can we check if the tRPC client is inferring the right types from the database? Let’s modify the code snippet below in ./client/index.ts to pass a number instead of a string as the description:

// ...
const createdDino = await trpc.dino.create.mutate({
  name: "Denosaur",
  description:
-   "A dinosaur that lives in the deno ecosystem. Eats Nodes for breakfast.",
+   100,
});
console.log("Created dino:", createdDino);
// ...

When we re-run the client:

deno task client
...
error: Uncaught (in promise) TRPCClientError: [
  {
    "code": "invalid_type",
    "expected": "string",
    "received": "number",
    "path": [
      "description"
    ],
    "message": "Expected string, received number"
  }
]
    at Function.from (file:///Users/andyjiang/Library/Caches/deno/npm/registry.npmjs.org/@trpc/client/11.0.0-rc.608/dist/TRPCClientError.mjs:35:20)
    at file:///Users/andyjiang/Library/Caches/deno/npm/registry.npmjs.org/@trpc/client/11.0.0-rc.608/dist/links/httpBatchStreamLink.mjs:118:56
    at eventLoopTick (ext:core/01_core.js:175:7)

tRPC successfully threw an invalid_type error, since it was expecting a string instead of a number.

What’s next?

Now that you have a basic understanding of how to use tRPC with Deno, you could:

  1. Build out an actual frontend using Next.js or React
  2. Add authentication to your API using tRPC middleware
  3. Implement real-time features using tRPC subscriptions
  4. Add input validation for more complex data structures
  5. Integrate with a proper database like PostgreSQL or use an ORM like Drizzle or Prisma
  6. Deploy your application to Deno Deploy or any public cloud via Docker

🦕 Happy type safety coding with Deno and tRPC!

🚨️ Want to learn more Deno? 🚨️

Check out our new Learn Deno tutorial series, where you’ll learn:

…and more, in short, bite-sized videos. New tutorials published every Tuesday and Thursday.