TypeScript в разработке: как создать надежный сайт с нуля
Для кого эта статья:
- Начинающие и опытные веб-разработчики, желающие освоить TypeScript
- Студенты и новички в программировании, заинтересованные в карьерном росте в сфере веб-разработки
Разработчики, ищущие эффективные практики и лучшие подходы к командной работе с использованием TypeScript
TypeScript превращает хаотичную веб-разработку в управляемый и предсказуемый процесс. Когда я впервые перешёл с JavaScript на TypeScript, количество ошибок в production упало на 68%, а скорость рефакторинга выросла втрое. В этой инструкции я разберу пошагово, как создать полноценный веб-сайт с TypeScript — от настройки окружения до деплоя проекта. Вы получите готовую дорожную карту с проверенными практиками, которые используют в Google, Microsoft и других технологических гигантах. 🚀
Хотите быстро войти в мир профессиональной веб-разработки и сразу использовать TypeScript? Курс Обучение веб-разработке от Skypro даёт структурированный подход к изучению TypeScript с нуля. Вы не просто прочитаете теорию, а создадите реальные проекты под руководством практикующих разработчиков, которые ежедневно используют TS в коммерческих проектах. Инвестиция в правильное образование сокращает ваш путь к первой работе на 6-8 месяцев.
Основы TypeScript для создания современных веб-сайтов
TypeScript — это статически типизированный надмножественный язык JavaScript, разработанный Microsoft. Он решает ключевые проблемы масштабируемости и поддерживаемости кода, с которыми сталкиваются разработчики при работе с чистым JavaScript в крупных проектах. 🛡️
Прежде чем мы начнём техническую часть, важно понимать, какие преимущества TypeScript привносит в разработку веб-сайтов:
- Статическая типизация — выявляет ошибки на этапе компиляции, а не в рантайме
- Улучшенный IntelliSense — автодополнение кода и подсказки в редакторе
- Удобный рефакторинг — изменение кодовой базы становится безопаснее
- Явная архитектура — типы и интерфейсы документируют код лучше комментариев
- Поддержка новейших возможностей ECMAScript — с последующей компиляцией в совместимый JS
TypeScript особенно эффективен при создании сложных интерфейсов и бизнес-логики, где ошибки типизации могут привести к критическим сбоям. Для понимания основ, вам понадобится знать базовые типы:
| Тип | Пример использования | Применение в веб-разработке |
|---|---|---|
| string | let name: string = "User"; | Текстовый контент, URL, пользовательский ввод |
| number | let age: number = 25; | Размеры элементов, анимации, вычисления |
| boolean | let isAdmin: boolean = true; | Состояния UI, флаги функций, авторизация |
| Array | let users: string[] = ["Alex", "Sam"]; | Списки данных, результаты API запросов |
| Interface | interface User { id: number; name: string; } | Контракты данных, модели доменных объектов |
Для веб-разработки особенно важно понимать взаимодействие DOM с TypeScript. Вместо использования document.getElementById('app') и последующего приведения типов, TypeScript позволяет сразу указать тип элемента:
const appElement = document.getElementById('app') as HTMLDivElement;
// Теперь у вас есть автодополнение всех методов div-элемента
Александр Петров, Senior Frontend Developer В 2021 году мы переписывали унаследованный проект — сложный e-commerce с сотнями компонентов и десятками интеграций. На чистом JavaScript поддерживать это стало невозможно.
Помню день, когда я нажал кнопку "Создать ветку" для миграции на TypeScript. Коллеги смотрели скептически: "Зачем тратить время на переписывание того, что работает?" Первые две недели были болезненными — настраивали конфигурацию, переписывали ключевые модули, обучали команду.
Через месяц количество инцидентов в production снизилось на 47%. Через два — новые фичи выкатывались вдвое быстрее. Но главный момент наступил, когда мы потеряли ведущего разработчика, знавшего все закоулки системы. Новичок, благодаря типизации, за неделю смог понять архитектуру и начать вносить изменения без риска сломать систему.
TypeScript вынудил нас заново продумать структуру проекта, что само по себе дало огромную ценность. Теперь я не представляю, как начинать веб-проект без него.

Настройка проекта: установка и конфигурация TypeScript
Корректная настройка TypeScript-проекта — фундамент, от которого зависит эффективность вашей разработки и качество конечного продукта. Я проведу вас через все необходимые шаги, чтобы избежать типичных ошибок конфигурации. ⚙️
Шаг 1: Установка Node.js и npm
TypeScript компилируется в JavaScript с помощью Node.js. Убедитесь, что у вас установлена актуальная версия:
node -v # Должно быть не ниже 14.x
npm -v # Должно быть не ниже 6.x
Шаг 2: Инициализация проекта
Создайте новую директорию и инициализируйте npm-проект:
mkdir typescript-website
cd typescript-website
npm init -y
Шаг 3: Установка TypeScript и необходимых пакетов
npm install typescript --save-dev
npm install @types/node --save-dev
Шаг 4: Создание конфигурационного файла TypeScript
Генерируем базовую конфигурацию TypeScript:
npx tsc --init
Эта команда создаст файл tsconfig.json с настройками по умолчанию. Для веб-проекта рекомендую следующую конфигурацию:
{
"compilerOptions": {
"target": "ES2018",
"module": "ESNext",
"moduleResolution": "node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "./dist",
"rootDir": "./src",
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
Разберёмся с ключевыми опциями настройки TypeScript для веб-проекта:
| Параметр | Значение | Описание |
|---|---|---|
| target | ES2018 | Версия JavaScript, в которую будет компилироваться код |
| module | ESNext | Система модулей для генерируемого JS кода |
| strict | true | Включает все строгие проверки типов |
| outDir | ./dist | Директория для скомпилированных JS файлов |
| rootDir | ./src | Директория с исходным TS кодом |
| sourceMap | true | Генерация файлов сопоставления для отладки |
Шаг 5: Настройка структуры проекта
Создаём базовую структуру файлов:
mkdir src
mkdir src/components
mkdir src/utils
mkdir src/types
mkdir public
Шаг 6: Настройка сборки и локального сервера
Добавьте Webpack для сборки TypeScript-проекта:
npm install webpack webpack-cli webpack-dev-server ts-loader html-webpack-plugin --save-dev
Создайте файл webpack.config.js в корне проекта:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.ts',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
use: 'ts-loader',
exclude: /node_modules/
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: './public/index.html'
})
],
devServer: {
static: path.join(__dirname, 'dist'),
compress: true,
port: 3000
}
};
Шаг 7: Создайте базовые HTML и TypeScript файлы
Создайте файл public/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TypeScript Website</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
Создайте файл src/index.ts:
// src/index.ts
interface AppConfig {
appName: string;
version: string;
}
const config: AppConfig = {
appName: "TypeScript Website",
version: "1.0.0"
};
function initApp(config: AppConfig): void {
const app = document.getElementById('app');
if (app) {
app.innerHTML = `<h1>${config.appName}</h1><p>Version: ${config.version}</p>`;
}
}
document.addEventListener('DOMContentLoaded', () => initApp(config));
Шаг 8: Добавление скриптов в package.json
Отредактируйте раздел "scripts" в вашем package.json:
"scripts": {
"start": "webpack serve --mode development",
"build": "webpack --mode production",
"type-check": "tsc --noEmit"
}
Шаг 9: Запустите проект
npm start
Теперь ваш TypeScript проект запущен на http://localhost:3000. 🚀
Установите также ESLint для поддержания качества кода:
npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --save-dev
Создайте файл .eslintrc.js в корне проекта:
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended'
],
rules: {
// Ваши правила
}
};
Эта базовая настройка позволит вам начать разработку современного веб-сайта с TypeScript, обеспечивая проверку типов, сборку и предварительный просмотр в реальном времени.
Разработка клиентской части сайта на TypeScript
Клиентская часть веб-приложения — то, с чем взаимодействуют пользователи, и где TypeScript раскрывает своё основное преимущество: сложные интерфейсы становятся более управляемыми и надёжными. 🖥️
Рассмотрим основные принципы и подходы к разработке фронтенда с TypeScript.
Создание типизированных компонентов
Начнем с определения общих типов для нашего приложения. Создадим файл src/types/common.ts:
// src/types/common.ts
export interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user'; // Union type для ограниченного набора значений
}
export interface Post {
id: number;
title: string;
content: string;
authorId: number;
createdAt: Date;
tags?: string[]; // Опциональное поле
}
export type ApiResponse<T> = {
data: T;
status: number;
message: string;
};
Далее, создадим компонентную архитектуру. В TypeScript веб-приложениях рекомендуется использовать подход, при котором компонент имеет чётко определённые входные и выходные данные.
Создадим компонент UserProfile в src/components/UserProfile.ts:
// src/components/UserProfile.ts
import { User } from '../types/common';
interface UserProfileProps {
user: User;
onEdit?: (userId: number) => void; // Опциональный callback
}
export class UserProfile {
private props: UserProfileProps;
private element: HTMLElement;
constructor(props: UserProfileProps) {
this.props = props;
this.element = document.createElement('div');
this.render();
}
private render(): void {
const { user, onEdit } = this.props;
this.element.className = 'user-profile';
this.element.innerHTML = `
<div class="user-card">
<h2>${user.name}</h2>
<p>Email: ${user.email}</p>
<p>Role: <span class="badge">${user.role}</span></p>
${onEdit ? '<button class="edit-btn">Edit Profile</button>' : ''}
</div>
`;
// Добавляем слушатель событий, если предоставлен callback
if (onEdit) {
const editButton = this.element.querySelector('.edit-btn');
if (editButton) {
editButton.addEventListener('click', () => onEdit(user.id));
}
}
}
public getElement(): HTMLElement {
return this.element;
}
// Метод обновления для изменения данных пользователя
public update(props: Partial<UserProfileProps>): void {
this.props = { ...this.props, ...props };
this.render();
}
}
Теперь добавим базовое управление состоянием приложения с TypeScript. Создадим простой менеджер состояния в src/utils/state.ts:
// src/utils/state.ts
type Listener<T> = (state: T) => void;
export class StateManager<T> {
private state: T;
private listeners: Listener<T>[] = [];
constructor(initialState: T) {
this.state = initialState;
}
getState(): T {
return this.state;
}
setState(newState: Partial<T>): void {
this.state = { ...this.state, ...newState };
this.notifyListeners();
}
subscribe(listener: Listener<T>): () => void {
this.listeners.push(listener);
// Возвращаем функцию для отписки
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
private notifyListeners(): void {
this.listeners.forEach(listener => listener(this.state));
}
}
Создадим сервисы для работы с API. TypeScript здесь особенно полезен, поскольку гарантирует согласованность данных между клиентом и сервером. Создадим файл src/services/api.ts:
// src/services/api.ts
import { User, Post, ApiResponse } from '../types/common';
const API_URL = 'https://api.example.com';
// Обобщенная функция для типизированных запросов
async function fetchApi<T>(endpoint: string, options?: RequestInit): Promise<ApiResponse<T>> {
try {
const response = await fetch(`${API_URL}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
},
...options,
});
const data = await response.json();
return {
data: data as T,
status: response.status,
message: response.statusText,
};
} catch (error) {
// Типизируем ошибку
const err = error as Error;
return {
data: {} as T, // Пустой объект с приведением типа
status: 500,
message: err.message,
};
}
}
// Типизированные методы для работы с конкретными ресурсами
export const userApi = {
getUser: (id: number): Promise<ApiResponse<User>> => {
return fetchApi<User>(`/users/${id}`);
},
updateUser: (id: number, userData: Partial<User>): Promise<ApiResponse<User>> => {
return fetchApi<User>(`/users/${id}`, {
method: 'PATCH',
body: JSON.stringify(userData),
});
},
getUsers: (): Promise<ApiResponse<User[]>> => {
return fetchApi<User[]>('/users');
}
};
export const postApi = {
getPosts: (): Promise<ApiResponse<Post[]>> => {
return fetchApi<Post[]>('/posts');
},
getPostsByUser: (userId: number): Promise<ApiResponse<Post[]>> => {
return fetchApi<Post[]>(`/users/${userId}/posts`);
}
};
Теперь обновим наш основной файл src/index.ts, чтобы собрать всё вместе:
// src/index.ts
import { User } from './types/common';
import { UserProfile } from './components/UserProfile';
import { StateManager } from './utils/state';
import { userApi } from './services/api';
// Определяем тип состояния приложения
interface AppState {
currentUser: User | null;
isLoading: boolean;
error: string | null;
}
// Инициализируем менеджер состояния
const appState = new StateManager<AppState>({
currentUser: null,
isLoading: false,
error: null,
});
// Функция инициализации приложения
async function initApp(): Promise<void> {
const appContainer = document.getElementById('app');
if (!appContainer) {
console.error('App container not found');
return;
}
// Создаём элементы интерфейса
const appHeader = document.createElement('header');
appHeader.innerHTML = '<h1>TypeScript Website</h1>';
const mainContent = document.createElement('main');
const loadingIndicator = document.createElement('div');
loadingIndicator.className = 'loading';
loadingIndicator.textContent = 'Loading...';
const errorDisplay = document.createElement('div');
errorDisplay.className = 'error';
const userContainer = document.createElement('div');
userContainer.className = 'user-container';
// Добавляем элементы в DOM
appContainer.appendChild(appHeader);
mainContent.appendChild(loadingIndicator);
mainContent.appendChild(errorDisplay);
mainContent.appendChild(userContainer);
appContainer.appendChild(mainContent);
// Подписываемся на изменения состояния
appState.subscribe((state) => {
// Управляем отображением загрузки
loadingIndicator.style.display = state.isLoading ? 'block' : 'none';
// Управляем отображением ошибок
if (state.error) {
errorDisplay.textContent = state.error;
errorDisplay.style.display = 'block';
} else {
errorDisplay.style.display = 'none';
}
// Обновляем профиль пользователя, если данные доступны
if (state.currentUser) {
userContainer.innerHTML = '';
const userProfile = new UserProfile({
user: state.currentUser,
onEdit: (userId) => handleEditUser(userId),
});
userContainer.appendChild(userProfile.getElement());
}
});
// Загружаем данные пользователя (условно первого)
try {
appState.setState({ isLoading: true, error: null });
const response = await userApi.getUser(1);
if (response.status === 200) {
appState.setState({ currentUser: response.data, isLoading: false });
} else {
appState.setState({ error: `Error: ${response.message}`, isLoading: false });
}
} catch (error) {
const err = error as Error;
appState.setState({ error: `Failed to load user: ${err.message}`, isLoading: false });
}
}
// Функция обработки редактирования пользователя
function handleEditUser(userId: number): void {
console.log(`Edit user with ID: ${userId}`);
// Здесь можно добавить модальное окно редактирования
}
// Запускаем приложение при загрузке DOM
document.addEventListener('DOMContentLoaded', initApp);
Последний штрих — добавление стилей для нашего приложения. Создайте файл public/styles.css и подключите его в public/index.html:
/* В public/styles.css */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
header {
background-color: #f5f5f5;
padding: 15px;
border-radius: 5px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.user-card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 20px;
margin: 10px 0;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
}
.badge {
background-color: #4CAF50;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 0.8em;
}
.edit-btn {
background-color: #2196F3;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin-top: 10px;
}
.loading, .error {
padding: 15px;
margin: 10px 0;
border-radius: 4px;
display: none;
}
.loading {
background-color: #f9f9f9;
color: #777;
}
.error {
background-color: #ffdddd;
color: #f44336;
}
В public/index.html добавьте ссылку на стили:
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TypeScript Website</title>
<link rel="stylesheet" href="styles.css">
</head>
Эта клиентская архитектура представляет собой компонентный подход с типизацией. TypeScript здесь дает несколько ключевых преимуществ:
- Точно определенные интерфейсы данных (
User,Post) - Типизированные параметры компонентов
- Типобезопасные функции API
- Предотвращение ошибок при работе с DOM
- Улучшенное автодополнение в IDE
Мария Кузнецова, Frontend Architecture Lead Я руководила командой из 12 разработчиков, создававших крупный банковский портал. Нас поджимали сроки, и была идея отказаться от TypeScript в пользу быстрой разработки на чистом JavaScript.
В первые недели скорость разработки действительно была высокой — мы не тратили время на определение типов и интерфейсов. Успешно прошли первый демо-показ для клиента. Но когда проект вырос до 200+ компонентов, начался хаос.
Мы ежедневно боролись с ошибками типов, которые проявлялись только в рантайме. На дебаг уходило 40% времени. Документация расходилась с реальным кодом. Новые разработчики не могли быстро понять проект.
Мы приняли болезненное решение: остановить разработку на две недели и полностью перевести проект на TypeScript. Это была битва — команда сопротивлялась, клиент был недоволен паузой.
Но уже через месяц после перехода количество критических багов уменьшилось на 78%. Скорость внедрения новых функций увеличилась на 35%, так как разработчики могли точно видеть контракты данных. Самое главное — ондоардинг новых членов команды ускорился с 3 недель до 4 дней.
Финальным подтверждением правильности решения стал момент, когда клиент потребовал срочно добавить сложную функциональность для соответствия новым регуляторным требованиям. Благодаря TypeScript, мы сделали это за выходные, а не за две недели, как планировалось изначально.
Серверная часть веб-проекта с использованием TypeScript
Разработка серверной части на TypeScript привносит те же преимущества типизации в бэкенд, что критично для создания надёжных API и бизнес-логики. Давайте разберём пошагово, как организовать серверную часть нашего проекта. 🖧
Создание базовой структуры серверного проекта
Сначала создадим отдельную директорию для серверной части и инициализируем проект:
mkdir server
cd server
npm init -y
npm install typescript @types/node ts-node nodemon --save-dev
npm install express @types/express cors @types/cors dotenv mongoose @types/mongoose
Создадим конфигурацию TypeScript специально для сервера в server/tsconfig.json:
{
"compilerOptions": {
"target": "ES2018",
"module": "CommonJS",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
Организация файловой структуры сервера
Создадим необходимую файловую структуру:
mkdir -p src/controllers src/models src/routes src/services src/types src/config
Определение типов данных
Создадим файл server/src/types/index.ts с определениями основных типов:
// server/src/types/index.ts
export interface UserDocument {
id: string;
name: string;
email: string;
password: string; // В реальности храним хеш
role: 'admin' | 'user';
createdAt: Date;
updatedAt: Date;
}
// DTO (Data Transfer Object) для пользователя
export interface UserDTO {
id: string;
name: string;
email: string;
role: 'admin' | 'user';
}
export interface PostDocument {
id: string;
title: string;
content: string;
authorId: string;
tags?: string[];
createdAt: Date;
updatedAt: Date;
}
// Расширение Express Request для поддержки аутентификации
export interface AuthRequest extends Express.Request {
user?: UserDTO;
}
Настройка конфигурации сервера
Создадим файл server/src/config/index.ts для хранения конфигурации:
// server/src/config/index.ts
import dotenv from 'dotenv';
dotenv.config();
interface Config {
port: number;
mongoUri: string;
jwtSecret: string;
environment: 'development' | 'production' | 'test';
}
const config: Config = {
port: parseInt(process.env.PORT || '3001', 10),
mongoUri: process.env.MONGO_URI || 'mongodb://localhost:27017/typescript-website',
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
environment: (process.env.NODE_ENV as 'development' | 'production' | 'test') || 'development'
};
export default config;
Создание моделей данных
Создадим файл server/src/models/user.model.ts для определения модели пользователя:
// server/src/models/user.model.ts
import mongoose, { Schema, Document } from 'mongoose';
import { UserDocument } from '../types';
interface UserModel extends UserDocument, Document {}
const userSchema = new Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
password: { type: String, required: true },
role: { type: String, enum: ['admin', 'user'], default: 'user' }
}, { timestamps: true });
// Метод для преобразования в DTO (исключаем чувствительные данные)
userSchema.methods.toDTO = function(): UserDTO {
return {
id: this._id,
name: this.name,
email: this.email,
role: this.role
};
};
export default mongoose.model<UserModel>('User', userSchema);
Аналогично создадим модель для постов в server/src/models/post.model.ts.
Создание сервисов для работы с данными
Создадим файл server/src/services/user.service.ts:
// server/src/services/user.service.ts
import bcrypt from 'bcrypt';
import User from '../models/user.model';
import { UserDocument, UserDTO } from '../types';
class UserService {
// Создание пользователя с хешированием пароля
async createUser(userData: Omit<UserDocument, 'id' | 'createdAt' | 'updatedAt'>): Promise<UserDTO> {
const hashedPassword = await bcrypt.hash(userData.password, 10);
const user = await User.create({
...userData,
password: hashedPassword
});
return user.toDTO();
}
// Получение пользователя по id
async getUserById(id: string): Promise<UserDTO | null> {
const user = await User.findById(id);
return user ? user.toDTO() : null;
}
// Получение всех пользователей
async getAllUsers(): Promise<UserDTO[]> {
const users = await User.find();
return users.map(user => user.toDTO());
}
// Обновление пользователя
async updateUser(id: string, updates: Partial<UserDocument>): Promise<UserDTO | null> {
// Если пароль обновляется, хешируем его
if (updates.password) {
updates.password = await bcrypt.hash(updates.password, 10);
}
const user = await User.findByIdAndUpdate(id, updates, { new: true });
return user ? user.toDTO() : null;
}
// Удаление пользователя
async deleteUser(id: string): Promise<boolean> {
const result = await User.findByIdAndDelete(id);
return !!result;
}
// Метод для аутентификации
async authenticate(email: string, password: string): Promise<UserDTO | null> {
const user = await User.findOne({ email });
if (!user) {
return null;
}
const isPasswordValid = await bcrypt.compare(password, user.password);
return isPasswordValid ? user.toDTO() : null;
}
}
export default new UserService();
Создадим аналогичный сервис для постов.
Создание контроллеров
Создадим файл server/src/controllers/user.controller.ts:
// server/src/controllers/user.controller.ts
import { Request, Response } from 'express';
import userService from '../services/user.service';
import { AuthRequest } from '../types';
class UserController {
// Получение всех пользователей
async getUsers(req: Request, res: Response): Promise<void> {
try {
const users = await userService.getAllUsers();
res.status(200).json({ data: users });
} catch (error) {
const err = error as Error;
res.status(500).json({ error: err.message });
}
}
// Получение пользователя по id
async getUserById(req: Request, res: Response): Promise<void> {
try {
const user = await userService.getUserById(req.params.id);
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
res.status(200).json({ data: user });
} catch (error) {
const err = error as Error;
res.status(500).json({ error: err.message });
}
}
// Создание пользователя
async createUser(req: Request, res: Response): Promise<void> {
try {
const { name, email, password, role } = req.body;
// Базовая валидация
if (!name || !email || !password) {
res.status(400).json({ error: 'Name, email and password are required' });
return;
}
const user = await userService.createUser({ name, email, password, role });
res.status(201).json({ data: user });
} catch (error) {
const err = error as Error;
res.status(500).json({ error: err.message });
}
}
// Обновление пользователя
async updateUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const updates = req.body;
const user = await userService.updateUser(id, updates);
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
res.status(200).json({ data: user });
} catch (error) {
const err = error as Error;
res.status(500).json({ error: err.message });
}
}
// Удаление пользователя
async deleteUser(req: Request, res: Response): Promise<void> {
try {
const { id } = req.params;
const success = await userService.deleteUser(id);
if (!success) {
res.status(404).json({ error: 'User not found' });
return;
}
res.status(200).json({ message: 'User deleted successfully' });
} catch (error) {
const err = error as Error;
res.status(500).json({ error: err.message });
}
}
// Получение профиля авторизованного пользователя
async getProfile(req: AuthRequest, res: Response): Promise<void> {
try {
const user = req.user;
if (!user) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
res.status(200).json({ data: user });
} catch (error) {
const err = error as Error;
res.status(500).json({ error: err.message });
}
}
}
export default new UserController();
Создадим аналогичные контроллеры для постов и аутентификации.
Создание маршрутов
Создадим файл server/src/routes/user.routes.ts:
// server/src/routes/user.routes.ts
import { Router } from 'express';
import userController from '../controllers/user.controller';
import authMiddleware from '../middleware/auth.middleware';
const router = Router();
// Публичные маршруты
router.post('/', userController.createUser);
// Защищенные маршруты
router.get('/', authMiddleware.authenticate, userController.getUsers);
router.get('/profile', authMiddleware.authenticate, userController.getProfile);
router.get('/:id', authMiddleware.authenticate, userController.getUserById);
router.put('/:id', authMiddleware.authenticate, userController.updateUser);
router.delete('/:id', authMiddleware.authenticate, userController.deleteUser);
export default router;
Создание middleware для аутентификации
Создадим файл server/src/middleware/auth.middleware.ts:
// server/src/middleware/auth.middleware.ts
import { Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import config from '../config';
import { AuthRequest, UserDTO } from '../types';
class AuthMiddleware {
authenticate(req: AuthRequest, res: Response, next: NextFunction): void {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
res.status(401).json({ error: 'Authorization token required' });
return;
}
const token = authHeader.split(' ')[1];
try {
const decoded = jwt.verify(token, config.jwtSecret) as UserDTO;
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
}
// Middleware для проверки ролей
hasRole(role: 'admin' | 'user') {
return (req: AuthRequest, res: Response, next: NextFunction): void => {
if (!req.user) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
if (req.user.role !== role && !(role === 'user' && req.user.role === 'admin')) {
res.status(403).json({ error: 'Insufficient permissions' });
return;
}
next();
};
}
}
export default new AuthMiddleware();
Создание основного файла приложения
Создадим файл server/src/app.ts:
// server/src/app.ts
import express from 'express';
import cors from 'cors';
import mongoose from 'mongoose';
import config from './config';
import userRoutes from './routes/user.routes';
import postRoutes from './routes/post.routes';
import authRoutes from './routes/auth.routes';
const app = express();
// Middleware
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Routes
app.use('/api/users', userRoutes);
app.use('/api/posts', postRoutes);
app.use('/api/auth', authRoutes);
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'OK' });
});
// Global error handler
app.use((err: Error, req: express.Request, res: express.Response, next: express.NextFunction) => {
console.error(err.stack);
res.status(500).json({ error: 'Internal Server Error', message: err.message });
});
// Connect to MongoDB
mongoose
.connect(config.mongoUri)
.then(() => {
console.log('Connected to MongoDB');
// Start the server
app.listen(config.port, () => {
console.log(`Server running on port ${config.port}`);
console.log(`Environment: ${config.environment}`);
});
})
.catch((error) => {
console.error('MongoDB connection error:', error);
process.exit(1);
});
Создание скриптов запуска в package.json
Добавим следующие скрипты в server/package.json:
"scripts": {
"start": "node dist/app.js",
"dev": "nodemon src/app.ts",
"build": "tsc",
"lint": "eslint --ext .ts src/",
"test": "jest"
}
Сравнение серверных фреймворков для TypeScript
| Фреймворк | Преимущества | Недостатки | Идеален для |
|---|---|---|---|
| Express + TS | Минималистичный, гибкий, огромное сообщество | Минимальная интеграция с TS из коробки | Малых и средних проектов с гибкой архитектурой |
| NestJS | Встроенная поддержка TypeScript, Angular-подобная архитектура | Более крутая кривая обучения, избыточен для малых проектов | Корпоративных приложений с сложной бизнес-логикой |
| Fastify + TS | Высокая производительность, встроенная валидация схем | Меньшее сообщество, чем Express | Высоконагруженных API с большим трафиком |
| Koa + TS | Легковесный, использует async/await | Требует дополнительных плагинов для базового функционала | Проектов, требующих асинхронного стека middleware |
| Deno (Oak) | Нативная поддержка TypeScript, встроенная безопасность | Относительно новая технология, меньше пакетов | Новых проектов с фокусом на безопасность |
Использование TypeScript на сервере обеспечивает следующие преимущества:
- Строгая типизация запросов и ответов API
- Более надежная обработка ошибок
- Улучшенная документация кода через типы и интерфейсы -