使用 Nuxt 和 tRPC 构建全栈 Typescript 应用程序

前言

最近我在学习 Nuxt.js 和 Nest.js 写前后端分离的项目时,当进行网络请求和 API 调用时,经常会不知道这个请求的提交参数、响应结果有什么数据字段,前端往往需要对后端返回的数据再创建一遍TS的类型,再封装对应的请求方法发送请求获取数据。

但这样为了更好的类型提示,无形之间又增加了工作量,我需要定义每个接口的 Response 与 Body 类型,就极易造成开发疲惫,不愿维护代码。

本文总共会接触到以下主要技术栈。

主要这些技术栈都与 typescript 相关,并且在 trpc 的示例应用中或多或少使用到,因此也是有必要了解一下。

Nuxt.js

安装

使用 cli 创建 Nuxt 3 应用程序

pnpm 复制代码
pnpm dlx nuxi@latest init <project-name>

如果创建失败,可以手动访问下载:codeload.github.com/nuxt/starte...

安装 tRPC 库和 Zod 以进行架构和参数验证。

pnpm 复制代码
pnpm add @trpc/server @trpc/client trpc-nuxt zod

配置 nuxt.config.ts

修改 nuxt.config.ts 将其转译为 ES5 包。

typescript 复制代码
// nuxt.config.ts

export default defineNuxtConfig({
+  build: {
+    transpile: ['trpc-nuxt']
+  }
})

配置后端服务

1. 创建 tRPC 路由器实例

最好的是在单独的文件中创建 tRPC 实例, 并导出可复用的函数而不是整个 tRPC 实例对象。

typescript 复制代码
// server/trpc/trpc.ts

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

const t = initTRPC.create();

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

接下来,我们将初始化主路由器实例,通常称为 appRouter 。然后我们需要导出一会儿将在客户端使用的路由器类型。

typescript 复制代码
// server/trpc/routers/index.ts

import { router } from './trpc';

export const appRouter = router({

// ...

});

export type AppRouter = typeof appRouter;

2. 添加查询

使用 publicProcedure.query() 向路由器添加查询。

typescript 复制代码
// server/trpc/routers/index.ts

import { PrismaClient } from '@prisma/client'
import { publicProcedure, router } from './trpc';

// 实例化 `PrismaClient` 
const prisma = new PrismaClient()

export const appRouter = router({
  userList: publicProcedure.query(async () => {
    return await prisma.user.findMany()
  }),
});

export type AppRouter = typeof appRouter;

这里用到了 prisma 这个库, 还不熟悉的小伙伴可以去看看文档, 这里就不多介绍.

或者像下面这样, 简单返回个数组

typescript 复制代码
// server/trpc/routers/index.ts

import { PrismaClient } from '@prisma/client'
import { publicProcedure, router } from './trpc';

// 实例化 `PrismaClient` 
const prisma = new PrismaClient()

// 定义个数组
const list = [
  { id: 1, name: 'test1', email: '123@qq.com' },
  { id: 2, name: 'test2', email: '123@qq.com' },
  { id: 3, name: 'test3', email: '123@qq.com' },
]

export const appRouter = router({
  userList: publicProcedure.query(async () => {
   return list
  }),
});

export type AppRouter = typeof appRouter;

3. 使用验证库校验客户端输入

Zod 中文文档

typescript 复制代码
// server/trpc/routers/index.ts

import { z } from 'zod'
import { PrismaClient } from '@prisma/client'
import { publicProcedure, router } from '../trpc'


const prisma = new PrismaClient()

const list = [
  { id: 1, name: 'test1', email: '123@qq.com' },
  { id: 2, name: 'test2', email: '123@qq.com' },
  { id: 3, name: 'test3', email: '123@qq.com' },
]

export const appRouter = router({
  userList: publicProcedure.query(async () => {
    return list
  }),

  userById: publicProcedure
    .input(z.number())
    .query(async (opts) => {
    
      // input: number
      const { input } = opts
      return list.find(item => item.id === input)
    }),
})


export type AppRouter = typeof appRouter

4. 提供 API 服务

由于 @trpc/client 没有集成 Nuxt.js, 所以我们使用 tRPC-Nuxt 这个库.

typescript 复制代码
// server/api/trpc/[trpc].ts

import { createNuxtApiHandler } from 'trpc-nuxt'
import { appRouter } from '~/server/trpc/routers'

// export API handler
export default createNuxtApiHandler({
  router: appRouter
})

在客户端上调用后端服务

1. 创建 tRPC 插件,以便可以在整个应用程序中访问 tRPC Client

不熟悉的小伙伴可以查看文档 Nuxt Plugins

typescript 复制代码
// plugins/trpc-client.ts

import type { AppRouter } from '~/server/trpc/routers';
import { createTRPCNuxtClient, httpBatchLink } from 'trpc-nuxt/client';

export default defineNuxtPlugin(() => {

  const trpcClient = createTRPCNuxtClient<AppRouter>({
    links: [
      httpBatchLink({
        url: '/api/trpc',
      }),
    ],
  });

  return {
    provide: {
      trpcClient,
    },
  };
});

2. 最后在应用程序中测试

现在一切准备就绪,我们可以在 app.vue 中测试 api 调用

vue 复制代码
// app.vue

<script setup lang="ts">
const { $trpcClient } = useNuxtApp()

// https://trpc-nuxt.vercel.app/get-started/tips/composables
// useQuery() 是 tRPC-Nuxt 框架里用 useAsyncData 包装了一层
const { data, execute, refresh } = await $trpcClient.userList.useQuery()

// @trpc/client 只有 query()
// const list = await $trpcClient.list.query()

const { data: user } = await $trpcClient.userById.useQuery(1) // 字符串类型会报错

</script>

<template>
  <div>
    <h1>user list</h1>
    <ul>
      <li v-for="item in data" :key="item.id">
        {{ `name: ${item.name} email: ${item.email}` }}
      </li>
    </ul>
    <h1>find user</h1>
    <div>
      {{ `name: ${user?.name} email: ${user?.email}` }}
    </div>
  </div>
</template>

输出

参考

Typescript 全栈最值得学习的技术栈 TRPC

Build A Full-Stack Typescript Application with Nuxt and tRPC

相关推荐
kaixin_learn_qt_ing13 小时前
了解RPC
网络·网络协议·rpc
BUG研究员_1 天前
LoadBalancer负载均衡和Nginx负载均衡区别理解
nginx·rpc·负载均衡
大霸王龙2 天前
远程过程调用(RPC,Remote Procedure Call)是一种协议
网络·python·网络协议·rpc
大霸王龙2 天前
Python中流行的RPC(Remote Procedure Call,远程过程调用)框架主要有以下几个:
网络·网络协议·rpc
阿杰同学5 天前
Docker核心概念总结
docker·容器·rpc
_nirvana_w_5 天前
深入探索 C++ 编程技巧:从案例中学习高效实践
c++·学习·rpc
jjw_zyfx5 天前
python rabbitmq实现简单/持久/广播/组播/topic/rpc消息异步发送可配置Django
python·rpc·rabbitmq
凌鲨6 天前
OpenLinkSaas 2025年1月开发计划
rpc·go·个人开发
p-knowledge7 天前
容器设计模式:Sidecar
网络协议·设计模式·rpc
zfoo-framework7 天前
rpc设计的再次思考20251215(以xdb为核心构建游戏框架)
rpc