Express — це мінімалістичний та гнучкий фреймворк для серверної частини на Node.js, який надає потужні можливості для створення API. У цій короткій серії ми розглянемо основні можливості Express.js.
Чому варто використовувати Express:
- Мінімалізм
Express.js має всі необхідні інструменти для створення API з нуля. Код простий, зрозумілий та легко почати працювати. - Популярність
Express.js використовується в багатьох додатках на Node.js і іноді з іншими середовищами виконання, такими як Deno і Bun. - Велика екосистема
Express.js існує вже довгий час.
Завдяки широкому впровадженню в спільноті Node.js, за ці роки було створено багато сторонніх пакетів для розширення функцій фреймворку. - Підтримка маршрутизації, проміжного програмного забезпечення (middleware), обробки помилок і т. д. з коробки.
- Якщо ви добре знайомі з чистим Node.js, Express.js буде вам зрозумілий дуже швидко.
- Базис для таких фреймворків, як Next.js і Nest.js
Налаштування проекту
Express.js — це фреймворк для Node.js, тому для використання Express вам потрібно мати встановлений Node.js.
Після налаштування Node.js використовуйте команду npm init
, щоб> npm init -y
Записано в D:\Medium\express-basics-and-beyond-medium\package.json:
{
"name": "express-basics-and-beyond-medium",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
```
Далі встановіть Express.js:
\> npm i express
Додано 65 пакетів, перевірено 66 пакетів за 2 с
Створіть файл app.js у кореневій теці та налаштуйте базовий сервер.
// app.js
const express = require('express');
const app = express();
app.listen(3000, () => {
console.log('Сервер запущено на порту 3000!');
});
Коротке пояснення
- Дві рядки на початку імпортують пакет Express і створюють його екземпляр.
const express = require('express');
const app = express();
- Рядок внизу відповідає за запуск сервера на конкретному порту.
Він приймає два параметри: номер порту та функцію зворотного виклику (callback function), яка виконується, коли сервер стартує.
app.listen(3000, () => {
console.log('Сервер запущено на порту 3000!');
});
Ви можете вибрати будь-який порт, який не використовується на вашій машині. У цьому випадку я обрав порт 3000. Якщо ви запустите app.js з термінала, ви побачите повідомлення в консолі:
\> node app.js
Сервер запущено на порту 3000!
Потім перейдіть у вашому браузері за адресою http://localhost:3000/ і подивіться, що відбудеться.
І ось помилка. Чому?
Для початку, при відвідуванні цієї URL-адреси відправляється GET-запит на сервер. Однак на сервері немає маршрутів, які б обробляли цей запит.
Сервер не знає, що робити.
Перший маршрут
Екземпляр додатку надає широкий набір методів для різних HTTP методів, зокрема:
app.get()
для GET запитівapp.post()
для POST запитівapp.put()
для PUT запитівapp.patch()
для PATCH запитівapp.delete()
для DELETE запитівapp.all()
для всіх варіантів маршрутів тощо.
Кожен обробник методу приймає щонайменше два параметри:
- URI маршруту (
app.get('/URI-path')
) - Метод зворотного виклику, який обробляє відповідь
(app.get('/path', (req, res) => {...})
).
Метод зворотного виклику також приймає два параметри: об'єкти Request
та Response
. Більше про це ми поговоримо пізніше.
Ось як створити простий маршрут для GET запиту:
const express = require('express');
const app = express();
// Приклад GET запиту
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000, () => {
console.log('Сервер запущено на порту 3000!');
});
Зупиніть сервер (за допомогою CTRL/CMD+C) і знову запустіть node app.js
.
Поверніться до браузера та натисніть клавішу перезавантаження.
Відповідь "Hello World!" з'являється на екрані, і помилка зникає.
Кілька маршрутів
Express.js дозволяє визначити будь-яку кількість маршрутів та надати кожному унікальну відповідь:
Однак слід враховувати порядок пріоритету. Якщо кілька маршрутів мають однаковий URI маршруту, буде викликано лише перший маршрут.
Асинхронні функції (Async/Await)
Хоча маршрути в Express.js за замовчуванням є асинхронними, фреймворк працює на зворотних викликах (callbacks).
Щоб працювати з API, що повертають проміси, за допомогою async await, необхідно додати ключове слово async
перед функцією зворотного виклику.
app.get('/', async (req, res) =\> {
// створюємо асинхронну операцію
const promise = Promise.resolve('Hello Async Await!');
// чекаємо на результат
const rawString = await promise;
res.send(rawString); // Hello Async Await!
});
Запит
Об'єкт запиту (Request) відповідає за читання вхідних даних від клієнта і їх передачу для подальшої обробки.
Об'єкт запиту (Request) містить методи, які надають інформацію про запит, такі як:
- Параметри
- Запити (Query)
- Заголовки (Headers)
- Cookies (потрібен cookie-parser middleware)
- Тіло запиту (Body) (потрібен body-parser middleware для версій < 4.13)
Динамічні маршрути
Будь-який маршрут, який ви створюєте, може мати динамічний набір параметрів.
Це може бути виражено як параметри маршруту або запит на сервер.
- Використання параметрів запиту
app.get('/:id', (req, res) =\> {
const id = req.params.id;
res.send(id); // 123
});
Приклад запиту:
\> curl http://localhost:3000/123
Це корисно, коли потрібно отримати або видалити певний об'єкт (наприклад, користувача з заданим ID) зі списку.
- Використання запиту
app.get('/', (req, res) =\> {
const query = req.query;
res.send(query);// {"name":"'Mirza'"}
});
Приклад запиту:
\> curl http://localhost:3000?name='Mirza'
Запит зазвичай використовується, коли потрібно працювати з кількома динамічними параметрами, такими як параметри пошуку або пагінація.
Тіло запиту
Маршрут запиту також може приймати тіло запиту. Це JSON-об'єкт, який містить майже нескінченну кількість полів або масивів полів.
Типові сценарії використання — це запити POST та PUT.
Щоб прочитати вхідне тіло запиту, спочатку потрібно налаштувати middleware для парсингу тіла запиту:
const express = require('express');
const app = express();
app.use(express.json()); // підключення middleware для парсингу тіла запиту
Після цього ви зможете читати тіло запиту:
app.post('/', (req, res) =\> {
const data = req.body; // читаємо тіло запиту
res.send(data.username); // Mirzly
});
Приклад запиту:
\> curl -X POST
-H "Content-Type: application/json"
--data '{"username":"Mirzly", "profession":"DEV"}'
http://localhost:3000
Відповідь
Об'єкт відповіді відповідає за відправку відповідної відповіді клієнту після обробки запиту.
Відповідь може бути різною:
- Надсилати різні типи вмісту (звичайний текст, HTML, JSON, бінарні дані)
- Використовувати різні коди статусу (200, 201, 204, 400, 404, 500 тощо)
- Встановлювати власні заголовки та cookies (cookie)
- Створювати редирект на тимчасовий маршрут або на зовсім новий домен
- Завантажувати файл на клієнт
- Рендерити шаблон сторінки
Відповідь для запиту
За замовчуванням, один запит може мати лише одну відповідь:
app> {
res.send('Hello World!');
});
Проте, при роботі з потоками (streams) або подіями, що надсилаються сервером (server-sent events), ви можете надсилати відповідь частинами, а потім надіслати остаточну відповідь після завершення:
app.get('/', (req, res) =\> {
res.write('1');
res.write('2');
res.write('3');
res.end();
});
Зауважте, що додаток отримає виняток через тайм-аут, якщо відповідь не буде надіслана.
Content-Type
Тип вмісту за замовчуванням, який надсилається сервером — це text/plain.
app.get('/', (req,> {
res.send('Hello World!'); // текст
});
Однак, якщо ви надсилаєте відповідь у вигляді чогось іншого, наприклад, JSON, Express.js автоматично встановить відповідні заголовки відповіді:
app.get('/', (req, res) =\> {
res.send({ data: 'Hello World!' }); // JSON
});
Це одна з основних функцій Express.js.
У той час як у базовому модулі HTTP Node.js вам доведеться вручну встановлювати правильний MIME тип для кожної відповіді (через заголовки відповіді):
- HTML (
Content-Type: text/HTML
) - JSON (
Content-Type: application/json
) - Бінарні дані (
Content-Type: application/octet-stream
)
Express.js автоматично вирішує це "за кулісами", надаючи простий метод для використання: — res.send()
.
Коди статусу
Можливо, ви запитувалися, який HTTP статус код було надіслано клієнту для всіх відповідей, що були відправлені раніше.
- За замовчуванням статус код відповіді — 200 (OK).
- У випадку виключення статус код відповіді буде 500 (Internal Server Exception — помилка внутрішнього сервера).
Express.js дозволяє вам надсилати будь-який статус код, який ви бажаєте.
201 Created (Створено):
app.get('/', (req, res) =\> {
res.status(201).send('Entity created!');
});
400 Bad Request (Невірний запит):
app.get('/', (req, res) =\> {
res.status(400).send('Check your data!');
});
401 Unauthorized Exception (Помилка авторизації):
app.get('/', (req, res) =\> {
res.status(401).send('Sign-in required!');
});
404 Not Found Exception (Не знайдено):
app.get('/', (req, res) =\> {
res.status(404).send('Data not found!');
});
500 Internal Server Exception (Внутрішня помилка сервера):
app.get('/', (req, res) =\> {
res.status(500).send('Something went wrong!');
});
І так далі.
Функції Middleware є однією з основних можливостей маршрутизатора Express.js.
Middleware (проміжне програмне забезпечення) — це функція, яка служить своєрідним "щитом" між запитом і відповіддю.
// Приклад middleware
app.use((req, res, next) =\> {
console.log(`${req.method} запит до '${req.url}'`);
next();
});
// Обробник маршруту
app.get('/', (req, res) =\> {
res.send('Привіт, світ!');
});
Оскільки middleware має доступ до запиту та відповіді, він може прочитати вхідну інформацію запиту і вирішити, чи дозволити запиту продовжити свій шлях, чи надіслати помилку клієнту, наприклад:
app.use((req, res, next) {
if (!req.header('access-token')) {
return res.status(401).send('Неавторизовано!');
}
next(); // виклик next() дозволяє цьому запиту продовжити
});
Зазвичай middleware можна виявити як функцію, яка інжектується за допомогою ключового слова use:
app.use(SOME-FUNCTION);
Раніше, коли я говорив про парсинг тіла запиту, я використовував вбудоване middleware body-parser:
const express = require('express');
const app = express();
app.use(express.json()); // middleware
Middleware можна використовувати для таких задач, як:
- Логування
- Валідація вхідних даних запиту
- Зміна робочого процесу програми (активація режиму технічного обслуговування)
- Захист маршрутів (автентифікація, обмеження запитів), тощо.
Три типи middleware:
- Глобальне middleware
- Middleware, що використовується для конкретних маршрутів
- Middleware для обробки помилок
Функції middleware — це велика тема, яка виходить за рамки цього вступного посібника.
Підключення фронтенду до бекенду
Цей розділ стосується підключення вашої веб-аплікації до серверу бекенду.
Це працює з будь-яким веб-фреймворком (таким як Vue, Angular, React) або з ванільним HTML JavaScript додатком, що працює в браузері.
Бек> {
res.send('Привіт, світ!');
});
app.listen(3000, () => {});
```
Фронтенд додаток:
Index.html
\
\
\
\
\
\Document\
\
Як тільки ви це зробите, HTML файл виконає JS скрипт і викличе бекенд API.
Проте, незважаючи на встановлене з’єднання, виникла проблема.
![pic](https://drive.javascript.org.ua/f6648e0f101_cjJ0jbH8UNTjZmzGzSMdbQ_png)
Ця помилка виникла через CORS і є очікуваною.
**Cross-Origin Resource Sharing** (CORS) визначає спосіб, яким клієнтські веб-додатки, завантажені в одному домені, можуть взаємодіяти з ресурсами в іншому домені.
У цьому випадку веб-сервер (HTML сайт) намагається зв'язатися з бекенд сервером, що CORS не забороняє. Щоб вирішити цю проблему, вам потрібно буде внести ваше фронтенд-додаток у білий список, налаштувавши CORS на бекенді.
Зупиніть сервер і встанов> npm i cors
Далі налаштуйте CORS у вашому додатку:
const express = require('express');
const app = express();
const cors = require('cors');
app.use(cors()); // підключено middleware для CORS
app.get('/hello', (req, res) =\> {
res.send('Hello World!');
});
Тепер перезапустіть сервер => node app.js
і здійсніть той самий запит з фронтенду.
Відповідь повинна бути 200 (OK), а повідомлення відповіді «Hello World!», так само як і на бекенді.
Змінна середовища — це значення, яке визначає користувач і яке може впливати на поведінку процесів на комп’ютері.
У додатках Node.js можна використовувати змінні середовища для зберігання закодованих рядків і секретів.
Вміст цього файлу ніколи не повинен бути завантажений на публічну гілку або в інше публічно доступне середовище.
Створіть файл змінних середовища (.env) в кореневій директорії та визначте змінні.
PORT=9999
SECRET_KEY=123ABC
Змінні зчитуються за допомогою об'єкта process.env
.
У новіших версіях Node.js (2024 і вище) ця функція доступна «з коробки» (пояснено тут).
Однак для старіших версій можна використовувати сторонній пакет dotenv.
Використання DotEnv
Ось як налаштувати все за допомогою dotenv.
- Встановіть пакет:
npm i dotenv
- Імпортуйте пакет на початку файлу app.js:
require('dotenv').config(); // \<-- ось тут
const express = require('express');
const app = express();
- Використовуйте змінні середовища в додатку:
require('dotenv').config();
const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000; // 3000 - значення за замовчуванням
const SECRET_KEY = process.env.SECRET_KEY;
app.get('/get-secret', (req, res) =\> {
res.send({ secret: SECRET_KEY });
});
app.listen(PORT, () =\> {
console.log(`Server started on port ${PORT}!`);
});
Тепер, якщо ви перезапустите сервер, ви помітите щось інше:
\> node app.js
Server started on port 9999!
Сервер зчитує порт із файлу змінних середовища (.env).
Раніше порт сервера був прописаний вручну (3000), але тепер він є динамічним. Це означає, що сервер може працювати на будь-якому порту, який надається базовою інфраструктурою.
Аналогічно, якщо ви зробите запит до localhost у браузері
(http://localhost:9999/get-secret
), ви отримаєте секретний ключ із файлу змінних середовища:
Запит:
\> curl http://localhost:9999/get-secret
Відповідь:
{"secret":"123ABC"}
Та сама логіка для налаштування та зчитування змінних середовища використовується з підключеннями до баз даних, авторизаційними ключами, ключами сторонніх API тощо.
До цього часу ви запускали сервер, щоразу вводячи одну й ту ж команду (node app.js
), і вручну перезапускали сервер кожного разу, коли вносили зміни.
У цьому розділі цей процес буде автоматизовано.
По-перше, встановіть пакет під назвою Nodemon як залежність для розробки:
npm i --save-dev nodemon
Далі, оновіть файл package.json. У розділі scripts
замініть стандартний скрипт:
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
На наступне:
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
},
Тепер замість запуску node app.js
, ви можете просто виконати команду:
\> npm start
Сервер запущено на порту 9999!
Ця команда підходить для виробничих збірок.
Для розробки ви можете використовувати скрипт dev
, щоб сервер перезавантажувався автоматично щоразу, коли ви натискаєте кнопку збереження:
\> npm run dev
\> nodemon app.js
[nodemon] 2.0.15
[nodemon] щоб перезапустити в будь-який час, введіть `rs`
[nodemon] спостерігає за шляхами: *.*
[nodemon] спостерігає за розширеннями: js,mjs,json
[nodemon] запускає `node app.js`
Сервер запущено на порту 9999!
Зверніть увагу, якщо ви використовуєте новішу версію Node.js, ви також можете використовувати вбудований режим спостереження.
"scripts": {
"start": "node app.js",
"dev": "node --watch app.js"
},
Завершення
Це швидкий старт до Express.js. У майбутніх розділах ми більш детально розглянемо маршрутизацію, функції проміжного програмного забезпечення (middleware), статичні файли, роботу з базою даних та деплоймент.
Слідкуйте за оновленнями!
Перекладено з: Express.js Basics & Beyond