Лекция Тесты
Современные системы обмена сообщениями требуют не только высокой скорости доставки, но и гарантированной адресности: каждое сообщение должно доходить именно до того пользователя, которому оно предназначено. В веб‑чатах это особенно критично — от корректной маршрутизации зависит удобство общения, безопасность и масштабируемость всей платформы. Laravel предоставляет удобные инструменты для организации WebSocket‑соединений и построения логики коммутации сообщений. В этой статье мы рассмотрим основные принципы адресной доставки, архитектурные подходы и практические решения, позволяющие реализовать надежную систему чатов на Laravel.
В Laravel принцип такой: WebSocket не “ищет пользователя сам”, а доставляет событие в канал, а уже канал делает сообщение доступным только тем клиентам, которым разрешено на него подписаться. Для чата между конкретными пользователями обычно используют private channels, а для групповых комнат с информацией о присутствующих — presence channels. Laravel для этого использует broadcasting, а из первого-party WebSocket-сервера сейчас предлагает Laravel Reverb; также событие можно отправлять с toOthers(), чтобы не вернуть его тому же клиенту, который только что отправил сообщение.
В системе чатов есть 5 ролей:
То есть логика работы не такая:
“отправить напрямую в сокет пользователя B”
А такая:
“отправить событие в канал chat.15, и доступ к нему имеют только участники этого диалога”

Допустим, есть личный чат между user 5 и user 9.
На фронтенде оба пользователя подключаются через Echo / Reverb.
Например:
private-chat.42
Где 42 — это id диалога, а не id пользователя.
Это делается через Broadcast::channel(...) в routes/channels.php.
Laravel требует authentication callbacks для private/presence channels, а доступ к таким каналам авторизуется отдельно.
Обычно:
Например MessageSent.
Все клиенты, подписанные на этот канал и прошедшие авторизацию, получают событие через WebSocket.
Никто вне канала это сообщение не получит.
Потому что ограничение делается не на уровне WebSocket-соединения, а на уровне:
какого канала касается событие
Это самый важный принцип.
То есть “коммутация” сообщений в чате — это по сути:
message -> event -> channel -> authorized subscribers
Для личных сообщений обычно есть 2 нормальных подхода.
Например:
chat.42
Где 42 — личный диалог между двумя людьми.
Это лучший вариант, потому что:
Идея такая:
Broadcast::channel('chat.{conversationId}', function ($user, $conversationId) {
return \App\Models\Conversation::query()
->whereKey($conversationId)
->whereHas('participants', fn ($q) => $q->where('users.id', $user->id))
->exists();
});
Смысл: в канал войдет только тот, кто реально является участником разговора.
Например:
users.9
Тогда сервер шлет событие конкретно в канал получателя.
Это подходит для:
Но сам текст чата удобнее вести через канал диалога, а не через канал пользователя.
диаграмма последовательности для чата на Laravel + WebSocket.
Ниже пример для сценария:
Диаграмма последовательности чата на Laravel + WebSocket.

Оба клиента открывают постоянное соединение с WebSocket server.
Для private channel Laravel проверяет, имеет ли пользователь право слушать этот чат.
Обычно это routes/channels.php:
Broadcast::channel('chat.{conversationId}', function ($user, $conversationId) {
return ConversationParticipant::query()
->where('conversation_id', $conversationId)
->where('user_id', $user->id)
->exists();
});
Если auth успешен, клиент подписывается на:
private-chat.42
User A шлет обычный HTTP POST в Laravel.
Laravel сохраняет сообщение в таблицу messages.
Laravel вызывает event вроде MessageSent, и он уходит в WebSocket server.
WebSocket server рассылает событие всем, кто подписан на private-chat.42.
User A │ │ POST /messages ▼ Laravel │ │ save ▼ Database │ │ broadcast MessageSent ▼ WebSocket Server │ ├──> User B └──> User A (optional)
Обычно сообщение идет так:
То есть WebSocket не заменяет БД, а дополняет ее.
Если не хочешь, чтобы отправитель получил свое же сообщение обратно через сокет:
broadcast(new MessageSent($message))->toOthers();
Тогда последовательность будет такой:

Тогда WebSocket часть не сработает для него в моменте, но сообщение все равно:
То есть:
если online -> получает по WebSocket если offline -> потом читает из БД
В Laravel-чате обычно участвуют:

Обычно делают так:
Для новых сообщений в переписке:
Для персональных вещей:
Например туда отправлять:
Так архитектура получается чище.
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
class MessageSent implements ShouldBroadcast
{
public function __construct(public Message $message)
{
}
public function broadcastOn(): array
{
return [
new PrivateChannel('chat.' . $this->message->conversation_id),
];
}
public function broadcastAs(): string
{
return 'message.sent';
}
public function broadcastWith(): array
{
return [
'id' => $this->message->id,
'conversation_id' => $this->message->conversation_id,
'sender_id' => $this->message->sender_id,
'text' => $this->message->text,
'created_at' => $this->message->created_at?->toIso8601String(),
];
}
}
Тут ключевое:
public function store(StoreMessageRequest $request)
{
$conversation = Conversation::findOrFail($request->conversation_id);
abort_unless(
$conversation->participants()->where('users.id', auth()->id())->exists(),
403
);
$message = Message::create([
'conversation_id' => $conversation->id,
'sender_id' => auth()->id(),
'text' => $request->text,
]);
broadcast(new MessageSent($message))->toOthers();
return response()->json([
'status' => 'ok',
'message' => $message,
]);
}
toOthers() полезен, чтобы текущий клиент не получил свой же broadcast второй раз, если он уже оптимистично показал сообщение локально. Laravel прямо поддерживает такой сценарий для broadcast events.
Примерно так:
Echo.private(`chat.${conversationId}`)
.listen('.message.sent', (e) => {
console.log('Новое сообщение', e);
addMessageToChat(e);
});
Смысл:
Вот тут главный ответ на твой вопрос про принцип коммутации:
Он обычно знает только:
То есть бизнес-коммутация находится в Laravel:
В routes/channels.php:
use Illuminate\Support\Facades\Broadcast;
use App\Models\Conversation;
Broadcast::channel('chat.{conversationId}', function ($user, $conversationId) {
return Conversation::query()
->whereKey($conversationId)
->whereHas('participants', function ($q) use ($user) {
$q->where('users.id', $user->id);
})
->exists();
});
Это фундамент безопасности.
Если сделать плохо, чужой пользователь сможет подписаться на чужой чат.
Минимально:
Тогда проверка доступа очень естественная:
есть ли пользователь в conversation_user.
Пример:
Тогда WebSocket ничего не доставит прямо сейчас.
Поэтому чат почти всегда строится так:
Это очень важный принцип.
WebSocket в чате — не хранилище, а быстрый транспорт.
Кроме самого текста сообщения:
В канал чата
В персональный канал пользователя
Private channel |
Presence channel |
|
Нужен, когда надо просто ограничить доступ. Подходит для:
|
Это private channel + список участников канала. но еще позволяют знать, кто сейчас в комнате. Подходит для:
Для простого личного чата private channel обычно достаточно. |
Новички часто делают канал так:
chat.{userId}
И пытаются слать “пользователю”.
Это годится для уведомлений, но не очень удобно для полноценного диалога.
Лучше думать не “кому”, а “какому разговору принадлежит событие”.
То есть не:
А:

Если есть toOthers(), то назад в тот же сокет A событие не вернется, а B получит его сразу.
Для современной реализации:
Laravel сейчас официально продвигает Reverb как first-party решение для real-time WebSocket-коммуникации, а установка broadcasting поддерживается через artisan-команду.
Laravel Echo вообще не делает запросы в БД.
Echo на фронтенде только:
Для личного чата цепочка обычно такая:
То есть БД обычно участвует в момент создания сообщения, потому что сообщение надо сохранить как источник истины. А вот сама доставка payload подписчикам идет не через БД, а через broadcasting + WebSocket-сервер.
Да, обычно именно так и делают.
Если ты уже создал Message и сформировал broadcastWith() или просто передал нужные поля в event, то WebSocket-сервер рассылает этот payload подписчикам без “бесконечных запросов в БД”. Получатели слушают событие по открытому сокету, а не polling-ом через SQL.
Примерно логика такая:
$message = Message::create([ 'conversation_id' => $conversation->id, 'sender_id' => auth()->id(), 'text' => $request->text, ]); broadcast(new MessageSent( id: $message->id, conversationId: $message->conversation_id, senderId: $message->sender_id, text: $message->text, ))->toOthers();
И дальше в событии:
public function broadcastWith(): array
{
return [
'id' => $this->id,
'conversation_id' => $this->conversationId,
'sender_id' => $this->senderId,
'text' => $this->text,
];
}
Тогда подписчики получают уже готовые данные.
Если у тебя современный стек Laravel с Reverb, то это не Node.js-цикл, а отдельный WebSocket server Reverb, который держит постоянные соединения и рассылает broadcast-события по каналам. Laravel прямо описывает Reverb как first-party WebSocket-решение, интегрированное с broadcasting.
Если же используется старый стек через сторонние сервисы или старый socket.io/node-сервер, тогда да — там может быть Node.js. Но Laravel Echo сам по себе не означает Node.js. Echo — это клиентская библиотека, а серверной частью может быть Reverb или другой broadcaster.
Есть два важных места:
Когда клиент подписывается на private-chat.42, Laravel выполняет auth-запрос и callback в Broadcast::channel(...), чтобы проверить, имеет ли пользователь доступ к каналу. Это HTTP-auth шаг, и внутри него ты сам можешь сделать запрос в БД, например проверить участие пользователя в разговоре.
Broadcast-события в Laravel часто отправляют через queue, а не синхронно в том же запросе. Тогда после сохранения сообщения событие идет в очередь, а worker уже публикует его в WebSocket-broadcaster. Это не означает постоянные запросы в БД на каждого подписчика, но инфраструктурно очередь может участвовать.
Не так:
Echo -> БД -> Node.js -> БД -> подписчик -> БД ...
А так:
frontend -> Laravel HTTP -> save in DB -> broadcast event -> WebSocket server -> subscribed clients
То есть:
Когда ты вызываешь toOthers(), Laravel использует socket ID, который Echo передает через X-Socket-ID, чтобы не отправлять broadcast обратно в то же самое соединение. Это тоже часть механики доставки, и она не требует БД для самой фильтрации текущего сокета.
Итог
Да, в нормальной реализации:
Могу дальше нарисовать тебе точную схему потока личного сообщения в Laravel: HTTP → DB → Event → Queue/Reverb → Echo, с пояснением где именно бывают запросы к БД, а где их уже нет.
разницы “в наличии бесконечного цикла” почти нет — он есть везде.
Разница в том, кто управляет этим циклом и на каком уровне ты работаешь.
Суть различия
Ты сам пишешь логику:
То есть ты управляешь event loop напрямую через код
Это уже готовый WebSocket-сервер поверх PHP-экосистемы Laravel:
То есть цикл есть, но ты его не видишь и не контролируешь напрямую
В Node.js |
В Reverb |
|
Это реальный event loop:
while (true) {
processEvents();
}
(упрощенно) |
Там тоже есть event loop (обычно на базе ReactPHP / Swoole-подобной модели), Он делает: while (true): слушать сокеты принимать события рассылать по каналам |
|
Он:
Ты прямо в него встраиваешься:
ws.on('message', (msg) => {
// твоя логика
});
|
НО ты взаимодействуешь так: broadcast(new MessageSent($message)); И все. |
Node.js — low-level подход
Ты думаешь:
Пример:
clients[userId].send(message);
Ты сам реализуешь коммутацию
Ты думаешь:
broadcast(new MessageSent())->to(new PrivateChannel('chat.42'));
Коммутацию делает Laravel через каналы
| Node.js | Reverb |
|
|
|
|
Ты сам:
|
Reverb:
|
|
Ключевая разница мышления “Отправить сообщение пользователю B” |
Ключевая разница мышления Опубликовать событие в канал chat.42” |
|
Где проверяется доступ Ты сам пишешь: if (user.id !== message.targetUserId) return; |
Где проверяется доступ
Broadcast::channel('chat.{id}', function ($user, $id) {
return in_array($user->id, conversationParticipants($id));
});
Проверка происходит один раз при подписке, а не при каждой отправке |
|
Производительность
|
Производительность
|
|
|
| Node.js — ты пишешь транспорт сам | Reverb — ты используешь готовую транспортную систему Laravel |
Важный момент
бесконечный цикл — это архитектура любого WebSocket-сервера:
Разница не в цикле, а в уровне абстракции.
Вот подробное сравнение Laravel Reverb vs Socket.IO vs Pusher по всем ключевым критериям
Сравнительная таблица
| Критерий | Laravel Reverb | Socket.IO | Pusher |
|---|---|---|---|
| Тип решения | Self-hosted (Laravel-first) | Self-hosted (Node.js) | SaaS (облако) |
| Уровень абстракции | High-level (через Laravel events/channels) | Medium (API + ручная логика) | Very high-level |
| Язык сервера | PHP (Laravel) | JavaScript / Node.js | Не нужен |
| Нужно писать WebSocket сервер? | нет | да | нет |
| Контроль над логикой | Средний | Максимальный | Минимальный |
| Коммутация сообщений | Через channels (Laravel) | Вручную (rooms / sockets) | Через channels |
| Авторизация каналов | Встроена (Laravel auth) | Нужно реализовать | Встроена |
| Работа с пользователями | Через Laravel user model | Сам реализуешь | Через API |
| Интеграция с Laravel | нативная | через API | через SDK |
| Frontend библиотека | Laravel Echo | Socket.IO client | Pusher JS / Echo |
| Нужен отдельный сервер | да (Reverb) | да (Node.js) | нет |
| Сложность старта | низкая | высокая | очень низкая |
| Гибкость | Средняя | очень высокая | Низкая |
| Масштабирование | Через Laravel infra (Redis, queues) | Сам (Redis adapter и т.д.) | Автоматическое |
| Горизонтальное масштабирование | Нужно настраивать | Нужно настраивать | Из коробки |
| Поддержка presence channels | да | нужно реализовать | да |
| Поддержка private channels | да | вручную | да |
| Fallback (long polling) | нет | есть | есть |
| Работа без WebSocket (firewall) | нет | да | да |
| Надежность | Зависит от тебя | Зависит от тебя | высокая |
| Latency (задержка) | Низкая | Низкая | Средняя (через облако) |
| Хранение сообщений | нет (твоя БД) | нет | нет |
| Очереди (queue) | встроены (Laravel) | сам | не нужно |
| Broadcast events | нативно | сам | есть |
| DevOps сложность | Средняя | высокая | минимальная |
| Стоимость | Бесплатно | Бесплатно | платно |
| Vendor lock-in | нет | нет | есть |
| Подходит для чатов | идеально | идеально | идеально |
| Подходит для high-load | да, но настраивать | да | да |
| Подходит для MVP | да | сложно | идеально |
| Подходит для enterprise | да | да | да |
| Ключевые отличия |
лучший выбор если ты на Laravel
минус: меньше гибкости чем Node.js |
если нужен полный контроль
минус:
|
если не хочешь сервер вообще
минус:
|
| Когда что выбирать |
Выбирай Reverb если:
|
Выбирай Socket.IO если:
|
Выбирай Pusher если:
|
| Модель | event → channel → subscribers | socket → user → emit | API → channel → subscribers |
Коммутация сообщений между конкретными пользователями в WebSocket‑чатах — это фундаментальный элемент любой современной коммуникационной платформы. Используя возможности Laravel, разработчик получает гибкий и масштабируемый инструмент для построения приватных каналов, управления подписками и маршрутизации событий. Правильная архитектура не только упрощает поддержку и расширение системы, но и обеспечивает пользователям стабильный и безопасный обмен сообщениями. Освоив эти принципы, можно создавать чаты, которые будут одинаково эффективны как для небольших команд, так и для крупных сообществ.
Reverb — Laravel way (баланс простоты и контроля)
Socket.IO — low-level мощь и гибкость
Pusher — zero-backend быстрый старт
Коммутация сообщений в Laravel-чате делается через broadcast channels: сервер сохраняет сообщение, публикует event в приватный канал разговора, а получить его могут только те пользователи, которых callback авторизации допустил в этот канал.
1. Какой компонент Laravel отвечает за отправку событий в WebSocket-сервер?
Подсказка: это механизм, который "рассылает" события наружу.
2. Какой пакет используется на фронтенде для подписки на каналы Laravel?
Подсказка: его название совпадает с эффектом повторения звука.
3. Что делает WebSocket в отличие от обычного HTTP?
Подсказка: соединение не закрывается после ответа.
4. Какой сервер используется в Laravel для WebSocket из коробки (новый подход)?
Подсказка: название связано с эффектом "реверберации".
5. Что такое канал (channel) в контексте Laravel Echo?
Подсказка: несколько пользователей могут "слушать" одно и то же.
6. Какой тип канала требует авторизации пользователя?
Подсказка: не каждый пользователь может туда попасть.
7. Где обычно описываются правила доступа к каналам?
Подсказка: файл прямо говорит о каналах.
8. Что делает метод broadcast() в Laravel?
Подсказка: это как "вещание".
9. Какое свойство события указывает, что его нужно транслировать?
Подсказка: название интерфейса говорит само за себя.
10. Как передается событие от backend к WebSocket серверу?
Подсказка: есть драйверы типа redis, pusher.
11. Какой драйвер часто используется для передачи событий?
Подсказка: быстрый in-memory storage.
12. Что делает Laravel Echo на клиенте?
Подсказка: он "слушает" сервер.
13. Что произойдет, если WebSocket соединение разорвется?
Подсказка: библиотеки обычно автоматически восстанавливают связь.
14. В чем преимущество WebSocket перед polling?
Подсказка: сервер сам пушит данные.
15. Какой тип канала позволяет общаться один-к-одному?
Подсказка: нужен контроль доступа.
16. Что добавляет Presence channel?
Подсказка: можно узнать кто онлайн.
17. Где обрабатывается событие после получения клиентом?
Подсказка: frontend-логика.
18. Что такое Reverb в Laravel?
Подсказка: он держит постоянные соединения.
19. Какое событие чаще всего триггерит отправку сообщения в чат?
Подсказка: пользователь нажимает "send".
20. Нужно ли обязательно сохранять сообщение в БД перед отправкой?
Подсказка: БД — опциональна в real-time системах.
21. Почему в real-time приложениях часто используют очередь (queue) при broadcasting?
Подсказка: пользователь не должен ждать завершения тяжелых операций.
22. Какой архитектурный паттерн реализуется при использовании событий и подписчиков?
Подсказка: есть "наблюдатели", которые реагируют на изменения.
23. Зачем в системе real-time может использоваться Redis между Laravel и WebSocket сервером?
Подсказка: быстрый посредник между компонентами.
24. Почему WebSocket сервер обычно выделяют отдельно от Laravel backend?
Подсказка: тысячи соединений требуют отдельной оптимизации.
25. Что происходит после dispatch события с ShouldBroadcast?
Подсказка: событие проходит через драйвер трансляции.
26. Какой риск возникает при использовании только public каналов?
Подсказка: доступ есть у всех без проверки.
27. Как Presence channel узнает список пользователей?
Подсказка: пользователи "входят" и "выходят" из канала.
28. Почему WebSocket лучше подходит для чатов, чем polling?
Подсказка: нет постоянных запросов от клиента.
29. Какой компонент отвечает за авторизацию private канала?
Подсказка: проверка описывается в специальном файле маршрутов каналов.
30. Что произойдет, если Redis недоступен при broadcasting?
Подсказка: цепочка передачи событий прерывается.
31. Как можно уменьшить нагрузку при большом количестве событий?
Подсказка: не все события нужно отправлять мгновенно по одному.
32. Почему важно сериализовать данные в событии?
Подсказка: данные должны быть пригодны для передачи.
33. Какой подход лучше для масштабирования WebSocket сервера?
Подсказка: несколько инстансов работают параллельно.
34. Что делает driver "pusher" в Laravel?
Подсказка: это SaaS для WebSocket.
35. Почему важно минимизировать payload событий?
Подсказка: чем меньше данные — тем быстрее доставка.
36. Что произойдет при большом количестве открытых WebSocket соединений?
Подсказка: каждое соединение требует ресурсов.
37. Какой слой отвечает за UI обновление после получения события?
Подсказка: изменения происходят в браузере.
38. Что произойдет, если клиент не подписан на канал?
Подсказка: подписка — обязательна.
39. Какой механизм помогает избежать дублирования сообщений?
Подсказка: каждое сообщение должно быть уникально.
40. Какой основной принцип real-time архитектуры?
Подсказка: инициатор — сервер, а не клиент.
41. Какое ограничение является одним из ключевых для WebSocket соединений?
Подсказка: каждый клиент держит открытое соединение.
42. Почему WebSocket может быть проблемой при работе через прокси или firewall?
Подсказка: не все сети любят постоянные соединения.
43. Что происходит при плохом интернет-соединении с WebSocket?
Подсказка: связь должна быть стабильной.
44. Почему WebSocket сложнее масштабировать, чем HTTP?
Подсказка: соединение нужно "держать" на сервере.
45. Какое ограничение связано с памятью сервера?
Подсказка: тысячи пользователей = тысячи соединений.
46. Почему нельзя полностью заменить HTTP на WebSocket?
Подсказка: CRUD-запросы удобнее через HTTP.
47. Какое ограничение есть у мобильных клиентов?
Подсказка: OS экономит батарею.
48. Почему важно ограничивать размер payload в WebSocket?
Подсказка: чем больше данные — тем медленнее передача.
49. Какое ограничение связано с балансировщиками нагрузки?
Подсказка: клиент должен попасть на тот же сервер.
50. Почему fallback (например, polling) иногда необходим?
Подсказка: не все клиенты поддерживают WebSocket.Fallback в WebSocket — это механизм, который используется, когда прямое WebSocket‑соединение невозможно (например, из‑за ограничений прокси или фаервола). В таких случаях приложение автоматически переключается на альтернативные протоколы (обычно HTTP‑долгие запросы, polling или streaming), сохраняя тот же API для разработчика.
51. В чем ключевое отличие передачи данных в WebSocket по сравнению с WebRTC?
Подсказка: один вариант использует централизованный сервер, другой — peer-to-peer.
52. Какой тип соединения используется в WebRTC для передачи данных между клиентами?
Подсказка: клиенты могут общаться напрямую без постоянного сервера-посредника.
53. Почему WebRTC сложнее в настройке, чем WebSocket?
Подсказка: нужны дополнительные серверы для установления соединения.
54. Какое различие в составе пакета данных между WebSocket и WebRTC?
Подсказка: один протокол ориентирован на медиа-стримы.
55. Какой протокол лежит в основе передачи пакетов в WebRTC?
Подсказка: используется протокол, оптимизированный под минимальную задержку.
56. Чем отличается структура пакета WebSocket от WebRTC DataChannel?
Подсказка: разный транспортный стек определяет формат передачи.
57. Как обозначается протокол WebSocket в URL?
Подсказка: это короткая аббревиатура от названия технологии.
58. Какой префикс используется для защищенного WebSocket соединения?
Подсказка: аналог https, но для WebSocket.
59. Как обозначается соединение WebRTC в URL?
Подсказка: соединение устанавливается не через прямой URL, а через signaling.
60. В чем основная сущность WebSocket Pub/Sub модели?
Подсказка: есть "подписка" и "публикация" событий.
61. Чем отличается WebSocket RPC от Pub/Sub?
Подсказка: один похож на API вызов, другой — на broadcast.
62. Какой подход (архитектура) лучше подходит для чатов и уведомлений?
Подсказка: клиент подписывается на нужные каналы, а затем сообщения рассылаются сразу многим соотвествующим подписчикам.
63. В чем ключевое отличие реализации private каналов от public в Laravel Echo?
Подсказка: перед подпиской выполняется проверка прав пользователя.
64. В чем основное отличие public каналов по сравнению с private?
/broadcasting/auth и проверку прав пользователя.Подсказка: любой клиент может подписаться без проверки.
64. Как производят подписку в Laravel Echo на приватные каналы?
window.Echo.join('chat'), авторизация не требуется.Подсказка: Вспомни, какие каналы требуют проверки прав доступа через Broadcast::channel() и маршрут /broadcasting/auth и какие есть особенность на клиентской части.
65. Что делает Laravel Echo «за кулисами» при подписке на приватный канал если програмист делает объявление и вызов window.Echo.private('chat.1').listen('MessageSent', (e) => { ... });?
/broadcasting/auth для проверки прав доступа. * а сервер проверяет права доступа через Broadcast::channel() в routes/channels.php Подсказка: Подумай, как Laravel Echo узнает, имеет ли пользователь право подключиться к приватному каналу. Что происходит между клиентом и сервером перед тем, как подписка станет возможной?
Комментарии
Оставить комментарий
Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)
Термины: Выполнение скриптов на стороне сервера PHP (LAMP) NodeJS (Backend)