Одним из ключевых аспектов разработки современных веб-приложений является строгая типизация данных, особенно при взаимодействии с API. Fresh, как фреймворк на базе Deno и TypeScript, предоставляет мощные инструменты для безопасного описания и проверки структуры ответов от серверных функций и маршрутов.
Fresh использует возможности TypeScript для статической типизации. Любой API-обработчик может возвращать объекты строго определённого типа. Это позволяет:
Типизация начинается с описания интерфейсов или типов:
interface User {
id: number;
name: string;
email: string;
}
interface ApiResponse<T> {
success: boolean;
dat a: T;
error?: string;
}
В Fresh маршруты представляют собой обычные функции, экспортируемые
из файлов в папке routes. Каждый маршрут может быть
типизирован с использованием обобщённых типов. Например:
import { HandlerContext } FROM "$fresh/server.ts";
export const handler = async (
req: Request,
ctx: HandlerContext
): Promise<Response> => {
const users: User[] = await getUsersFromDb();
const response: ApiResponse<User[]> = { success: true, data: users };
return new Response(JSON.stringify(response), {
headers: { "Content-Type": "application/json" },
});
};
В этом примере ApiResponse<User[]> гарантирует,
что клиент всегда получит объект с полями success и
data, где data строго массив
пользователей.
Хотя TypeScript обеспечивает статическую типизацию, данные,
приходящие из внешних источников (например, из базы данных или сторонних
API), требуют проверки во время выполнения. Fresh не навязывает
конкретную библиотеку для валидации, но интеграция с zod
или superstruct является стандартной практикой:
import { z } FROM "zod";
const userSchema = z.object({
id: z.number(),
name: z.string(),
email: z.string().email(),
});
async function getValidatedUsers(): Promise<User[]> {
const rawUsers = await getUsersFromDb();
return rawUsers.map(user => userSchema.parse(user));
}
Использование схем позволяет получать ранние ошибки при несоответствии структуры данных и предотвращает баги на клиенте.
API часто требует обработки query-параметров. Типизация их значений также повышает надёжность кода.
interface UsersQuery {
LIMIT?: number;
offset?: number;
}
export const handler = async (req: Request) => {
const url = new URL(req.url);
const query: UsersQuery = {
LIMIT: url.searchParams.has("limit")
? Number(url.searchParams.get("limit"))
: undefined,
offset: url.searchParams.has("offset")
? Number(url.searchParams.get("offset"))
: undefined,
};
const users = await getUsersFromDb(query.limit, query.offset);
const response: ApiResponse<User[]> = { success: true, data: users };
return new Response(JSON.stringify(response), { headers: { "Content-Type": "application/json" } });
};
Тип UsersQuery фиксирует структуру параметров запроса и
позволяет безопасно использовать их внутри обработчика.
API должен корректно обрабатывать ошибки и возвращать их в предсказуемом формате. Для этого создаются отдельные типы:
interface ApiError {
success: false;
error: string;
}
function createErrorResponse(message: string): Response {
const response: ApiError = { success: false, error: message };
return new Response(JSON.stringify(response), { status: 400, headers: { "Content-Type": "application/json" } });
}
Типизация ошибок позволяет клиенту однозначно понимать структуру ответа и корректно обрабатывать неудачные запросы.
На стороне фронтенда типизация API ответов обеспечивает безопасное использование данных:
async function fetchUsers(): Promise<User[]> {
const res = await fetch("/api/users");
const json: ApiResponse<User[]> = await res.json();
if (!json.success) throw new Error(json.error);
return json.data;
}
Использование обобщённого типа ApiResponse<User[]>
гарантирует, что тип data всегда известен и корректно
проверяется TypeScript.
<T>) для повторно
используемых форматов данных.zod или
superstruct.Типизация API в Fresh не ограничивается только компиляцией — она формирует архитектурную основу для безопасного обмена данными между клиентом и сервером, делая код более надёжным и читаемым.