Вступ
При створенні додатків реального часу, таких як чати або інформаційні панелі, ми зазвичай розглядаємо Node.js та його численні фреймворки. У цьому посібнику я покажу вам найпродуктивніший спосіб створювати додатки реального часу за допомогою Zod і TypeScript.
Більшість з вас, можливо, подумає, що я буду говорити про новий фреймворк JavaScript, але якщо ви достатньо довго були в спільноті JavaScript, ви могли вже почути про Meteor.js.
У цьому посібнику я покажу вам новий погляд на створення додатків Meteor і чому він такий продуктивний. Наприкінці у вас буде робочий чат додаток реального часу, який використовує Zod для валідацій і повну підтримку TypeScript для ваших API.
Початок роботи
Переконайтеся, що у вас встановлений meteor на вашій машині.
Якщо у вас ще немає встановленого Meteor, ви можете виконати наступну команду:
npx meteor
Це встановить інструмент командного рядка Meteor на вашу машину.
Створення проекту
meteor create
Він запитає вас про назву вашого нового додатку та яку основу ви хочете використати. Я вибрав super-chat як назву нашого додатку і стартову основу TypeScript.
Далі ми можемо зайти в каталог і почати встановлювати пакети, необхідні для нашого посібника:
cd super-chat &&
meteor npm i meteor-rpc @tanstack/react-query zod react-router-dom@6
Пакети, які ми встановлюємо, є необхідними для роботи meteor-rpc:
- zod: для валідації на рівні виконання
- @tanstack/react-query: для запитів даних на клієнті
- meteor-rpc: для абстракції методів Meteor і публікацій у зручний та сучасний спосіб.
Ми також встановлюємо react-router-dom для маршрутизації в нашому додатку.
Налаштування нашого React додатку
Перш ніж продовжити, нам потрібно налаштувати наш React додаток. Ви можете слідувати керівництвам react-query і react-router, або ви можете вставити цей фрагмент у ваш imports/ui/App.tsx:
import React, { Suspense } from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter, Routes, Route } from "react-router-dom";const queryClient = new QueryClient();
export const App = () => (
Loading...}>
Hello from Super-Chat!
}
/>
Chat!} />
);
Далі ми видаляємо Hello.tsx та Info.tsx, оскільки вони нам не потрібні.
Запуск нашого додатку
Перше, що нам потрібно зробити, це видалити весь код з main.ts, а потім створити точку входу для сервера за допомогою createModule з meteor-rpc; на цьому етапі ваш файл main.ts повинен виглядати ось так:
import { createModule } from "meteor-rpc";
const server = createModule().build();
export type Server = typeof server;
Колекція чату
Нам спершу потрібно створити нашу ChatCollection для зберігання повідомлень та груп чатів. Для цього ми створимо в server/chat/model.ts нашу ChatCollection.
Код повинен виглядати ось так:
import { Mongo } from "meteor/mongo";
export interface Message {
text: string;
who: string;
createdAt: Date;
}
export interface Chat {
_id?: string;
messages: Message[];
createdAt: Date;
}
export const ChatCollection = new Mongo.Collection("chat");
Наш додаток не буде дуже складним, і це буде наша єдина колекція. Ми будемо зберігати кожне повідомлення з кожного чату, яке містить who (хто надіслав повідомлення), text (текст повідомлення) і дату створення.
Модуль чату
Тепер нам потрібно подумати, які методи чи функції потрібні нашому чату. Кожен чат-додаток має ці функції:
- можна створювати кімнати чату;
- можна надсилати повідомлення в кімнату чату;
- можна бачити всі кімнати чату в реальному часі;
- можна бачити бесіду в реальному часі;
З урахуванням цього, використаємо createModule з meteor-rpc і module.addPublication для функцій у реальному часі, а також module.addMethod для віддалених викликів.
Я створю файл TypeScript в server/chat/module.ts, який ми будемо використовувати як точку входу для наших функцій.
import { createModule } from "meteor-rpc";
import { ChatCollection } from "./model";
import { z } from "zod";
export const ChatModule =
createModule("chat")
.addMethod("createRoom", z.void(), async () => {
return ChatCollection.insertAsync({ createdAt: new Date(), messages: [] });
})
.addMethod(
"sendMessage",
z.object({ chatId: z.string(), message: z.string(), user: z.string() }),
async ({ chatId, message, user }) => {
return ChatCollection.updateAsync(
{ _id: chatId },
{
$push: {
messages: { text: message, who: user, createdAt: new Date() },
},
}
);
}
)
.addPublication("room", z.string(), (chatId) => {
return ChatCollection.find({ _id: chatId });
})
.addPublication("rooms", z.void(), () => {
return ChatCollection.find();
})
.buildSubmodule(); // це дуже важливо, не забудьте викликати цей метод
Voilá! Ми майже все налаштували на сервері. Тепер ми повинні зареєструвати цей модуль у файлі server/main.ts, і після цього можна переходити до клієнтської частини.
У файлі server/main.ts буде виглядати так, коли ми зареєструємо наш чат-модуль:
import { createModule } from "meteor-rpc";
import { ChatModule } from "./chat/module";
const server = createModule().addSubmodule(ChatModule).build();
export type Server = typeof server;
І з цим наш бекенд готовий!
Клієнтська частина
Наш додаток наразі має лише imports/ui/App.tsx і не має взаємодії з сервером. Щоб змінити це, ми повинні створити точку входу для нашого API, щоб імпортувати серверні виклики API. У моєму додатку я називатиму цю точку входу client.ts, і вона буде знаходитися в imports/api/client.ts. Також ми повинні очистити нашу папку API, видаливши файл links.ts. Ось як виглядає мій server.ts:
import { createClient } from "meteor-rpc";
import type { Server } from "/server/main";
export const client = createClient();
Важливо імпортувати лише тип Server з сервера; якщо ви спробуєте імпортувати будь-що інше, наша збірка не вдасться, тому що Meteor захищає нас від імпорту серверних файлів і, ймовірно, витоку конфіденційних файлів.
Типи підходять, оскільки вони видаляються на етапі збірки.
Ця змінна server в межах клієнтської області — це те, що ми будемо використовувати для взаємодії з нашим сервером.
Головна сторінка
Перше, що ми повинні створити на нашій клієнтській частині, — це головну сторінку, на якій повинна бути кнопка для створення нової кімнати чату та посилання для всіх доступних кімнат чату; з урахуванням цих вимог ми повинні створити файл Main.tsx у нашій директорії imports/ui, цей файл має виглядати ось так:
import React from "react";
import { client } from "../api/client";
import { useNavigate } from "react-router-dom";
export const Main = () => {
const navigate = useNavigate();
const { data: rooms } = client.chat.rooms.usePublication();
const createChatRoom = async () => {
const room = await client.chat.createTheRoom();
navigate(`/chat/${room}`);
};
return (
Ласкаво просимо до чату!
Натисніть, щоб створити нову кімнату чату або виберіть чат із списку нижче
Чати:
{rooms.length === 0 && ( У нас ще немає кімнат, чому б не створити одну? )}
{rooms?.map((room) => (
Чат кімната {room._id}
))}
);
};
Тепер ми повинні оновити наш App.tsx, щоб включити сторінку Main.tsx:
// ....
Loading...}>
}
/>
// ....
Наш додаток має виглядати ось так:
Однак наша сторінка чату все ще виглядає порожньою, коли ми натискаємо, щоб приєднатися до кімнати або створити нову. Це потрібно виправити!
Сторінка чату
По-перше, ми повинні створити файл Chat.tsx в imports/ui; в цьому компоненті ми повинні використати наш параметр chatId, щоб передати його на сервер, щоб він знав, в якій кімнаті потрібно додавати повідомлення, а також у якій кімнаті ми очікуємо оновлення.
Також ми повинні створити та перевірити наші поля введення.
В кінці ми повинні мати файл, який виглядатиме ось так:
import React, { useReducer } from "react";
import { client } from "../api/client";
import { useNavigate, useParams } from "react-router-dom";
export const Chat = () => {
let { chatId } = useParams();
const navigate = useNavigate();
const {
data: [chatRoom],
} = client.chat.room.usePublication(chatId as string);
const [state, dispatch] = useReducer(
(
state: { who: string; message: string },
action: { type: string; value: string }
) => {
switch (action.type) {
case "who":
return { ...state, who: action.value };
case "message":
return { ...state, message: action.value };
default:
return state;
}
},
{ who: "", message: "" }
);
const sendMessage = async () => {
if (state.who === "" || state.message === "") {
alert("Будь ласка, заповніть обидва поля");
return;
}
await client.chat.sendMessage({
chatId: chatId as string,
message: state.message,
user: state.who,
});
dispatch({ type: "message", value: "" });
};
return (
navigate("/")}>Повернутися на головну сторінку
Чат в кімнаті: {chatId}
Хто: dispatch({ type: "who", value: e.target.value })} value={state.who} />
Повідомлення: dispatch({ type: "message", value: e.target.value })} value={state.message} />
Надіслати повідомлення
Повідомлення:
{chatRoom.messages.length === 0 && Немає повідомлень}
{chatRoom.messages.map((message, i) => (
{message.who}: {message.text}
))}
);
};
Це виглядає великим, але терпіти! У цьому компоненті ми слухаємо кожне повідомлення в рамках розмови за її chatId. Ми можемо викликати метод sendMessage, визначений на сервері. Також у нас є валідація для перевірки перед відправкою повідомлень.
Не забувайте додати цей компонент до нашого маршрутизатора у App.tsx.
Подивимося, як це працює на практиці:
Розгортання
Тепер, коли наш додаток готовий, ми можемо його розгорнути. Все, що потрібно зробити, це виконати цю команду, і ваш додаток буде розгорнуто в Galaxy Cloud безкоштовно з включеною MongoDB:
meteor deploy .meteorapp.com - free - mongo
Висновок
Одна з найкращих функцій, яку meteor-rpc додає до наших Meteor додатків, — це структурований об'єкт для виклику; іншими словами, кожен метод, що визначений і має IntelliSense; якщо ви ще не тестували це самі, ви можете побачити це в цьому відео:
Ви можете переглянути повний вихідний код тут, а якщо у вас виникли проблеми з пакетом meteor-rpc, будь ласка, дайте нам знати або в репозиторії пакету, або зв'яжіться зі мною через мій X акаунт.
Перекладено з: The fastest way of creating a real-time app with Zod and TypeScript