使用 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

相关推荐
Amd7942 天前
Nuxt Kit 中的布局管理
前端·web开发·nuxt.js·布局管理·代码示例·addlayout·页面结构
OldGj_2 天前
一次RPC调用过程是怎么样的?
网络·网络协议·rpc
小于负无穷3 天前
Go 中 RPC 的使用教程
开发语言·后端·rpc·golang
重生之豪哥4 天前
Rabbitmq中得RPC调用代码详解
qt·rpc·rabbitmq
Geho5 天前
定时任务调用OpenFegin无token认证异常
java·spring boot·rpc·openfegin
靖海雷军9 天前
一. rpc基本学习
网络协议·学习·rpc
c1tenj29 天前
SpringCloud nacos
java·spring cloud·rpc
哒哒-blog10 天前
【go-zero】api与rpc使用etcd服务发现
rpc·golang·etcd·go-zero
Flying_Fish_roe11 天前
RPC的实现原理架构
网络·网络协议·rpc
漆黑的莫莫12 天前
经验笔记:RPC与高性能NIO框架
笔记·rpc·nio