Оглавление
- Введение
- Установка
- Структура папок проекта
- Серверный скрипт
- Маршрутизатор
- Инициализация Vue Query
- Клиент tRPC
- Компонент приложения
- Приложение и примеры
- Больше полезных ссылок
Введение
Недавно я гуглил о тенденциях в веб-разработке, чтобы узнать о современных инструментах/либах/фреймворках, и наткнулся на tRPC.
tRPC означает TypeScript remote procedure call, и, как вы можете прочитать на его домашней странице, его цель состоит в том, чтобы легко иметь End-to-end typesafe API. По сути, это позволяет вам предоставлять серверные функции, которые можно вызывать из вашего клиента, вашего фронтенда, используя все преимущества TS.
Официальный сайт tRPC, хорошая коллекция примеров и документация.
tRPC — это еще один способ обеспечить корректное взаимодействие между клиентом и сервером (через вызовы api). Возможно, вы уже думаете о GraphQL, но с tRPC вам не нужно учить новый язык, и он не является схемой. В то время как GraphQL — это схема и язык, которые вы используете для детализации «формы» функций, которые вы можете вызывать с сервера.
Эксперимент: Почему бы не попробовать, используя последнюю версию Vue, Vite, TypeScript и попытаться подключить tRPC и посмотреть, что получится?
Я попытался найти проекты на базе Vue, использующие tRPC, и подавляющее большинство моих результатов было основано на React/Next.js… Поэтому я решил просто начать с проекта на базе React, а затем экспериментировать дальше.
Примечания:
— Я буду ссылаться на все соответствующие ресурсы по всей статье.
— Это просто экспериментальная идея, чтобы подключить несколько современных пакетов и создать очень простой проект.
— Эта статья больше предназначена для людей, которые уже имеют некоторый опыт в веб-разработке, однако я постараюсь дать некоторые дополнительные объяснения.
Установка
В качестве отправной точки я посмотрел отличное видео Джека Херрингтона «tRPC: умные и простые API», следовал его шагам и думал, насколько сложно будет использовать Vue 3 и Vue Query вместо React и React Query соответственно.
В следующем разделе показано, как выглядит окончательная структура папок, основанная на шагах Джека и после модификации для использования Vue.
Структура папок проекта
Это монорепо, использующее рабочие пространства yarn.
Проект сервера находится в папке api-server, а проект фронтенда — в папке client.
И сервер, и клиент запускаются запуском yarn start
на корневом дире, как вы можете видеть в package.json в корневой папке:
Скрипт сервера
Это код сервера, где мы создаем наше экспресс-приложение и говорим ему использовать cors (чтобы разрешить вызовы с порта 3000 на 8080), а также использовать промежуточное ПО trpcExpress и зарегистрировать маршрутизатор.
// packagesapi-serverindex.ts
import express from 'express';
import * as trpcExpress from '@trpc/server/adapters/express';
import { appRouter } from './router/app';
import cors from 'cors';
const main = async () => {
const app = express();
app.use(cors());
const port = 8080;
app.use(
'/trpc',
trpcExpress.createExpressMiddleware({
router: appRouter,
createContext: () => null,
})
);
app.listen(port, () => {
console.log(`api-server listening at http://localhost:${port}`);
});
};
main();
Маршрутизатор
Следующий код показывает маршрутизатор, который содержит точки доступа:
- 2 конечные точки запроса (аналогично конечной точке GET для остальных):
- приветствия
- getMessages
- 1 конечная точка мутации (аналогично конечной точке rest POST):
- addMessage
Примечание: помимо добавления данных, мутация может также обновлять или удалять данные.
Вы также можете видеть, что я использую zod, который представляет собой «TypeScript-first schema declaration and validation library».
Этот пакет будет использоваться для проверки моих входных данных для запросов/мутаций (при необходимости эти проверки могут даже выдавать сообщения о проверке).
z.string().uuid({ message: "Invalid UUID" });
Примечание: Вы также можете использовать zod для извлечения типов из объектов zod, сохраняя их как типы и повторно используя их где угодно:
// packagesapi-serverrouterapp.ts
import * as trpc from '@trpc/server';
import { z } from 'zod';
import { v4 as uuidv4 } from 'uuid';
export interface ChatMessage {
id: string;
user: string;
message: string;
}
const messages: ChatMessage[] = [
{ id: uuidv4(), user: 'User1', message: 'This is my the first message!' },
{ id: uuidv4(), user: 'User2', message: 'Hello there 🎉' },
];
export const appRouter = trpc
.router()
.query('greetings', {
resolve() {
return {
message: 'Greetings from /trpc/greetings:)',
};
},
})
.query('getMessages', {
input: z.number().default(10),
resolve({ input }) {
return messages.slice(-input);
},
})
.mutation('addMessage', {
input: z.object({
user: z.string(),
message: z.string(),
}),
resolve({ input }) {
const newMessage: ChatMessage = {
id: uuidv4(),
...input,
};
messages.push(newMessage);
return input;
},
});
export type AppRouter = typeof appRouter;
В этом случае сообщения будут храниться только в памяти, потому что я не использую для этого БД. (и это ускоряет процесс демонстрации).
Также можно создавать различные маршрутизаторы, которые будут содержать различные запросы/мутации, а затем вы можете объединить маршрутизаторы, чтобы легко получить доступ к определенному запросу из маршрутизатора на клиенте.
Инициализация Vue Query
Вот как инициализируется vue-запрос через VueQueryPlugin в файле main.ts, который затем используется экземпляром приложения Vue:
// packagesclientsrcmain.ts
import { createApp } from 'vue';
import { VueQueryPlugin } from 'vue-query';
import './style.css';
import App from './App.vue';
createApp(App).use(VueQueryPlugin).mount('#app');
Зачем вообще использовать Vue Query, спросите вы?
«Я мог бы сделать все вызовы api с помощью fetch/axios, верно?».
Верно, однако этот пакет предлагает изящные возможности из коробки, такие как кэширование, повторная попытка, повторная выборка, бесконечный запрос (для бесконечной прокрутки) и т.д. Вот некоторые проблемы, которые могут возникнуть в вашем проекте с увеличением его сложности (взято из официальной документации):
- Кэширование… (возможно, самая сложная вещь в программировании).
- Дедупликация нескольких запросов на одни и те же данные в один запрос
- Обновление «устаревших» данных в фоновом режиме
- Знание, когда данные «устарели».
- Отражение обновлений данных как можно быстрее
- Оптимизация производительности, такая как пагинация и ленивая загрузка данных
- Управление памятью и сборка мусора для состояния сервера
- Мемоизация результатов запросов с помощью структурного разделения
Кроме того, хуки предлагают набор стандартных реквизитов/функций для использования в вашем приложении. Пример использования хука useQuery:
Примечание: Данные, к которым вам нужно получить доступ, находятся в удобно названном реквизите data.
клиент tRPC
Здесь мы указываем url, который нам нужно использовать при вызове клиента tRPC, а также типы, которые мы можем использовать, получая данные от AppRouter. (Позже мы импортируем этот trpc const в компонент App.vue):
// packagesclientsrcapitrpc.ts
import { createTRPCClient } from '@trpc/client';
import { AppRouter } from 'api-server/router/app';
export const trpc = createTRPCClient<AppRouter>({
url: 'http://localhost:8080/trpc',
});
Компонент App
Для простоты я решил выполнить клиентские вызовы tRPC именно в этом компоненте.
Примечание: я использую скриптовую настройку Vue и пока что получаю от этого удовольствие 🙂
<template>
<div class="trpc-example">
<h1>Vue 3 + vue-query + tRPC example</h1>
<Error
v-if="getMessagesHasError"
error-message="Something went wrong - cannot fetch data"
cta-text="Refetch data"
@click="refetch()"
/>
<Error
v-if="addMessageHasError"
error-message="Something went wrong - cannot submit message"
cta-text="Reset error"
@click="reset"
/>
<div v-if="showFormAndMessages" class="trpc-example__container">
<SendMessageForm :form="form" @submit-form="handleSubmitForm" />
<h2 v-if="isLoading">Data is being loaded</h2>
<Message v-for="chatMessage in data" :key="chatMessage.id" :chat-message="chatMessage" />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive } from 'vue';
import Message from './components/Message.vue';
import SendMessageForm from './components/SendMessageForm.vue';
import Error from './components/Error.vue';
import { useQuery, useMutation, useQueryClient } from 'vue-query';
import { trpc } from './api/trpc';
import { Form } from '../types';
const queryClient = useQueryClient();
const form = reactive({
user: '',
message: '',
});
const getMessages = () => trpc.query('getMessages');
const {
isError: getMessagesHasError,
isLoading,
data,
refetch,
} = useQuery('getMessages', getMessages, {
refetchOnWindowFocus: false,
});
const addMessage = (form: Form) => trpc.mutation('addMessage', form);
const { error: addMessageHasError, mutate, reset } = useMutation('addMessage', addMessage);
const handleSubmitForm = () => {
mutate(form, {
onSuccess: () => {
queryClient.invalidateQueries('getMessages');
},
});
};
const showFormAndMessages = computed(() => {
return !getMessagesHasError.value && !addMessageHasError.value;
});
</script>
Приложение и примеры
Лучший способ взаимодействия с этим проектом — это, конечно, запустить его локально и посмотреть, что вы можете с ним сделать. Но вот несколько примеров:
Вот как выглядит клиент (да, я знаю, пользовательский интерфейс выглядит потрясающе!). Vue.js devtools также отображает информацию о запросах:
Данные, поступающие из /trpc/greetings:
Данные, поступающие из /trpc/getMessages:
Примеры изменения функций на стороне сервера и соблюдения проверок безопасности TS на клиенте:
Вы также можете переименовывать свои серверные функции с клиента (по какой-то причине я не смог переименовать символ с сервера):
Пример блокировки запроса запроса и последующего вызова функции refetch и ее повторных попыток:
Пример блокировки запроса на мутацию и последующего вызова функции reset. Это сбрасывает состояние ошибки:
Другие полезные ссылки
- Мое репо: https://github.com/alousilva/express-vue-trpc
- Алекс, создатель tRPC: https://twitter.com/alexdotjs
- Тео — ping․gg, интервью с Алексом: https://www.youtube.com/watch?v=Mm3Z5c1Linw (кстати, у Тео есть тонна интересного контента на его канале youtube).
- Учитесь с Джейсоном, интервью с Алексом: https://www.youtube.com/watch?v=GryES84SSEU
Я могу создать еще одно репо для изучения более реалистичного проекта с использованием Nuxt, tRPC, Vue Query, где я подключаюсь к базе данных и использую ORM Prisma, аналогично тому, что Алекс сделал в этом довольно аккуратном стартовом репо: https://github.com/trpc/examples-next-prisma-starter.
Надеюсь, вы нашли эту статью полезной, и она позволила вам открыть для себя что-то новое сегодня 🙂