Основи Express.js та його можливості

Express — це мінімалістичний та гнучкий фреймворк для серверної частини на Node.js, який надає потужні можливості для створення API. У цій короткій серії ми розглянемо основні можливості Express.js.

pic

Чому варто використовувати 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 с

pic

Створіть файл 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/ і подивіться, що відбудеться.

pic

І ось помилка. Чому?
Для початку, при відвідуванні цієї 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.
Поверніться до браузера та натисніть клавішу перезавантаження.

pic

Відповідь "Hello World!" з'являється на екрані, і помилка зникає.

Кілька маршрутів

Express.js дозволяє визначити будь-яку кількість маршрутів та надати кожному унікальну відповідь:

pic

Однак слід враховувати порядок пріоритету. Якщо кілька маршрутів мають однаковий URI маршруту, буде викликано лише перший маршрут.

pic

Асинхронні функції (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!  
});

pic

Запит

Об'єкт запиту (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!');  
});

І так далі.

pic

Функції 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 — це велика тема, яка виходить за рамки цього вступного посібника.

pic

Підключення фронтенду до бекенду

Цей розділ стосується підключення вашої веб-аплікації до серверу бекенду.
Це працює з будь-яким веб-фреймворком (таким як 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!», так само як і на бекенді.

pic

pic

Змінна середовища — це значення, яке визначає користувач і яке може впливати на поведінку процесів на комп’ютері.
У додатках 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 тощо.

pic

До цього часу ви запускали сервер, щоразу вводячи одну й ту ж команду (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

Leave a Reply

Your email address will not be published. Required fields are marked *