Меня зовут Виктор, я фронтенд-работчик в Admitad. Моя команда делает личный кабинет клиентов. Недавно я в очередной раз столкнулся с типичной проблемой: для создания нового функционала фронтенд и бэкенд нужно было реализовывать параллельно. Но как делать фронт, не имея 100% рабочих эндпойнтов на бэкенде? Сегодня я расскажу о том, какие подходы применял, и разберу их плюсы и минусы.
Особых сложностей в этом нет, особенно, если вы уже знакомы с 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}`);
});
О более продвинутом можно почитать тут.
К плюсам данного подхода я отношу серьезные возможности в настройке сервера и огромное количество туториалов по его использованию. Основные минусы, на мой взгляд, в том, что необходимо запускать такой сервер отдельно от разрабатываемого приложения.
Итак, наши инструменты:
Две основные концепции с которыми работает MSW, это:
Методы rest:
Пример из документации:
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()
Пройдемся по папкам:
npx create-react-app studentList
Далее установил faker и Mock Service Worker как devDependencies:
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 запущен.
Значит все правильно настроили.
// /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
})
)
})
// /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, например:
Company
Возвращает информацию связанную с компанией, это может быть: companySuffix, catchPhrase, или множество других полей, нам понадобится метод companyName.
Internet
Тут нам понадобятся методы email, url. Названия говорят сами за себя.
Datatype
Генерирует случайную последовательность таких типов, как:
// /src/mocks/db.ts
import { factory } from '@mswjs/data';
import {StudentModel} from './models'
export const db = factory({
student: StudentModel
})
Запустив приложение, в браузере увидим сгенерированный список студентов:
Пример отображения данных, сгенерированных с помощью faker.js и полученных приложением по запросу GET /students
По каждому запросу в консоли можно увидеть подробную информацию от MSW:
Для того, чтобы сервис-воркер перестал перехватывать тот или иной запрос, можно просто отключить/удалить соответствущий обработчик.
О проблеме
Как я уже сказал выше, проблема заключалась в том, что мне было необходимо отобразить полученные в результате запроса на сервер данные в одном из разделов приложения. Но на бэкенде этот эндпойнт был еще не готов. Стандартный выход из подобной ситуации это использование 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 - генерирует данные в соответствии с нашими потребностями;
Две основные концепции с которыми работает MSW, это:
- request handler - обработчик запроса, который определяет, должен ли быть замокан запрос
- response resolver - функция, принимающая перехваченный запрос и возвращающая замоканный ответ
Обработчики запросов
MSW содержит в себе два набора методов для создания обработчиков для работы с API: rest и graphql.Методы rest:
- rest.get()
- rest.post()
- rest.put()
- rest.patch()
- rest.delete()
- rest.options()
Пример из документации:
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 находится все, что отвечает за состояние приложения
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, например:
Company
Возвращает информацию связанную с компанией, это может быть: companySuffix, catchPhrase, или множество других полей, нам понадобится метод companyName.
Internet
Тут нам понадобятся методы email, url. Названия говорят сами за себя.
Datatype
Генерирует случайную последовательность таких типов, как:
- number - целые числа, предел которых можно ограничить полями min и max
- float - действительные числа
- arrayElements - массив
- и т.д.
// /src/mocks/db.ts
import { factory } from '@mswjs/data';
import {StudentModel} from './models'
export const db = factory({
student: StudentModel
})
Запустив приложение, в браузере увидим сгенерированный список студентов:
По каждому запросу в консоли можно увидеть подробную информацию от MSW:
Для того, чтобы сервис-воркер перестал перехватывать тот или иной запрос, можно просто отключить/удалить соответствущий обработчик.
Вывод
Описанный подход существенно упрощает и ускоряет разработку, поскольку делает фронтенд-команду менее зависимой от бэкенда. Основными преимуществами в таком походе для меня стали:- возможность запуска mock-сервера непосредственно из основного проекта
- легкое переключение между реальными и фейковыми эндпойнтами
- быстрая и гибкая настройка обработчиков запросов
Моки без лишней мороки с mswjs+faker.js
Привет, Хабр! Меня зовут Виктор, я фронтенд-работчик в Admitad. Моя команда делает личный кабинет клиентов. Недавно я в очередной раз столкнулся с типичной проблемой: для создания нового функционала...
habr.com