Привет, Хабр! Меня зовут Виктор, я фронтенд-работчик в Admitad. Моя команда делает личный кабинет клиентов. Недавно я в очередной раз столкнулся с типичной проблемой: для создания нового функционала фронтенд и бэкенд нужно было реализовывать параллельно. Но как делать фронт, не имея 100% рабочих эндпойнтов на бэкенде? Сегодня я расскажу о том, какие подходы применял, и разберу их плюсы и минусы.
О проблеме
Как я уже сказал выше, проблема заключалась в том, что мне было необходимо отобразить полученные в результате запроса на сервер данные в одном из разделов приложения. Но на бэкенде этот эндпойнт был еще не готов. Стандартный выход из подобной ситуации это использование mock-данных.
Mock-объект - тип объектов, реализующих заданные аспекты моделируемого программного окружения (c)Википедия
Как организовать получение mock-данных?
Для решения проблемы есть несколько вариантов. Начнем с самого популярного.
Пишем сервер на node.js
Мы всегда можем написать свой mock-server на node.js используя такие фреймворки, как express или koa.
Особых сложностей в этом нет, особенно, если вы уже знакомы с node.js. Например, на express код простейшего сервера будет выглядеть таким образом:
import express from 'express'; const app = express(); const port = 3000; app.get('/', (req, res) => { res.send('This is Main!'); }); app.listen(port, () => { console.log(`App listening at http://localhost:${port}`); });
О более продвинутом можно почитать тут.
К плюсам данного подхода я отношу серьезные возможности в настройке сервера и огромное количество туториалов по его использованию. Основные минусы, на мой взгляд, в том, что необходимо запускать такой сервер отдельно от разрабатываемого приложения.
Используем внешние API сервисы
Уверен, многие знакомы с таким сервисом готовых API, как jsonplaceholder.typicode.com, в котором есть как готовые энднпойнты, например, /posts, /comments..., так и возможность добавлять свои, причем, либо создав в репозитории своего проекта файл db.json с фейковыми данными, либо с помощью приложения Mockend. Главный, на мой взгляд, минус заключается в том, что такой подход не позволяет использовать например POST-запросы.
Используем Mock Service Worker + Faker
Этот способ мне показался наиболее удобным, поскольку позволяет вести разработку в пределах основного проекта, легко подключая необходимые эндпойнты и отключая уже ненужные. В отличие от предыдущего способа, он предоставляет такие возможности как обработка всех REST-запросов и генерация набора данных с помощью моделей.
Итак, наши инструменты:
Mock Service Worker(далее MSW) - перехватывает запросы на сетевом уровне в браузере, обрабатывает их и возвращает ответы;
Faker - генерирует данные в соответствии с нашими потребностями;
Работает всё следующим образом: настраиваем service worker, который будет обрабатывать наши сетевые запросы и возвращать сгенерированные нами фэйковые данные, пока бэкенд в разработке. Таким образом работа двух команд может вестись параллельно. А когда бэкенд будет готов, достаточно будет сделать сборку без флага, запускающего mock-сервис.
Две основные концепции с которыми работает MSW, это:
request handler - обработчик запроса, который определяет, должен ли быть замокан запрос
response resolver - функция, принимающая перехваченный запрос и возвращающая замоканный ответ
Обработчики запросов
MSW содержит в себе два набора методов для создания обработчиков для работы с API: rest и graphql.
Методы rest:
rest.get()rest.post()rest.put()rest.patch()rest.delete()rest.options()
Первым аргументом методы принимают URL запроса, а вторым response resolver.
Пример из документации:
import { setupWorker, rest } from 'msw' const worker = setupWorker( rest.get('/users/:userId', (req, res, ctx) => { const { userId } = req.params return res( ctx.json({ id: userId, firstName: 'John', lastName: 'Maverick', }), ) }), ) worker.start()
Пример
Я создал простой проект с использованием mswjs + faker в качестве mock-сервера. Код проекта находится в репозитории. Фронт написан на React+TypeScript, redux в качестве стейт менеджера в связке с redux-saga, для стилей Sass. Файловая структура такая:

Пройдемся по папкам:
в api хранится реализация отправки сетевых запросов
в components понятно, компоненты
в mocks все, что связано с mock-сервером
в modules вынесен функционал связанный с каждой отдельной сущностью, в моем случае это Students
в store находится все, что отвечает за состояние приложения
Для создания проекта я использовал create-react-app:
npx create-react-app studentList
Далее установил faker и Mock Service Worker как devDependencies:
Настройки MSW
Для начала инициализируем Mock Service Worker используя следующую команду:
npx msw init public/ --save
В результате в папке /public будет создан файл mockServiceWorker.js со всеми необходимыми настройками для подключения сервис-воркера для mock-сервера.
После чего создадим файл src/mocks/browser.js, в котором подключим обработчики запросов:
// src/mocks/browser.js import { setupWorker } from 'msw'; import { handlers } from './handlers'; export const worker = setupWorker(...handlers);
Внимание! Использовать Mock Service Worker в production сборке крайне не рекомендуется!
Используем переменную окружения NODE_ENV, для проверки окружения, для которого собирается приложение в src/index.js:
// src/index.js import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; if (process.env.NODE_ENV === 'development') { const { worker } = require('./mocks/browser'); worker.start(); } ReactDOM.render(<App />, document.getElementById('root'));
После выполнения команды npm start и запуска приложения откроем браузер и увидим в консоли Developer Tools сообщение о том, что mock-server запущен.

Значит все правильно настроили.
Подводные камни
Описанный пример взят из документации, но с ним все не так просто. Если приложение сразу после загрузки отправляет сетевые запросы (а именно так и работает наше приложение), вначале посыпятся 404-е ответы. Так происходит, потому что регистрация сервис-воркера - процесс асинхронный. Поэтому старт приложения и необходимые для этого запросы необходимо выполнить после окончания этого процесса:
Добавляем обработчики запросов
Список студентов я получаю по запросу GET /students. Создадим для него обработчик:
// /src/mocks/handlers/students.ts import {rest} from 'msw'; import { STUDENTS_LIST_ROUTE_MASK } from '../mocks.constants'; import {db} from '../db'; import { IStudent } from '../../components/StudentCard/StudentCard.types'; export const getStudentsHandler = rest.get(STUDENTS_LIST_ROUTE_MASK, ( req, res, ctx)=>{ // Генерируем мо модели массив из шести студентов // и возвращаем его в ответе let students:IStudent[] = []; for (let i = 0; i < 6; i += 1) { students.push(db.student.create()); } return res( ctx.json({ students }) ) })
Модель данных
Для генерации списка студентов используем библиотеку для моделирования данных @mswjs/data и faker.
// /src/mocks/models/StudentModel import { primaryKey } from '@mswjs/data'; import faker from 'faker'; export const StudentModel = { id: primaryKey(() => faker.random.number(10000).toString()), firstName: ()=> faker.name.firstName(), lastName: () => faker.name.lastName(), age: () => faker.datatype.number({min: 18, max: 69}), email: () => faker.internet.email(), phone: () => faker.phone.phoneNumber(('+7 (###) ###-##-##')), city: () => faker.address.cityName(), company: () => faker.company.companyName(), avatar: () => faker.image.avatar(), information: () => faker.lorem.words(10), };
В каждой модели прописываем данные, которые faker будет генерировать на каждый запрос. Апишка фейкера выглядит немного необычно, но довольно удобно, это очень дружелюбный DSL, реализованный с помощью цепочек выполнения, разделенный по категориям, которых более 20 (!). Используем следующие категории:
Image
Содержит в себе методы для генерации изображений. Например, для того чтобы сгенерировать URL, содержащий аватар 128x128 пикселей нужно вызывать
faker.image.avatar()
Таким образом мы получим URL, например:
https://cdn.fakercloud.com/avatars/eduardostuart_128.jpg
Company
Возвращает информацию связанную с компанией, это может быть: companySuffix, catchPhrase, или множество других полей, нам понадобится метод companyName.
Internet
Тут нам понадобятся методы email, url. Названия говорят сами за себя.
Datatype
Генерирует случайную последовательность таких типов, как:
number - целые числа, предел которых можно ограничить полями min и max
float - действительные числа
arrayElements - массив
и т.д.
В файле db.ts для подключения созданной модели используем функцию factory из @mswjs/data:
// /src/mocks/db.ts import { factory } from '@mswjs/data'; import {StudentModel} from './models' export const db = factory({ student: StudentModel })
Запустив приложение, в браузере увидим сгенерированный список студентов:

По каждому запросу в консоли можно увидеть подробную информацию от MSW:

Для того, чтобы сервис-воркер перестал перехватывать тот или иной запрос, можно просто отключить/удалить соответствущий обработчик.
Вывод
Описанный подход существенно упрощает и ускоряет разработку, поскольку делает фронтенд-команду менее зависимой от бэкенда. Основными преимуществами в таком походе для меня стали:
возможность запуска mock-сервера непосредственно из основного проекта
легкое переключение между реальными и фейковыми эндпойнтами
быстрая и гибкая настройка обработчиков запросов
Делитесь в комментариях мнениями о таком варианте mock-сервера.
