В Современном API, часть 1 мы рассмотрели, как настроить среду разработки для Node API. Здесь, во второй части, мы начнем писать код. Мы напишем некоторую логику запуска API, создадим корневой маршрут (GET /), который отображает информацию об API, и настроим набор тестов, чтобы начать тестирование конечных точек и проверку ответов.

TL;DR: https://github.com/decentorganization/decent-api

Запуск API

Наши «стартовые» сценарии в package.json выполняют src/index.js, так что давайте начнем с этого.

src/index.js

Импортирует библиотеку http, чтобы мы могли слушать запросы и отвечать на них.

Импортирует dotenv и configиспользует его, чтобы мы могли получать локальные переменные среды при запуске API.

Импортирует debug и создает его экземпляр, который является хорошей библиотекой для помощи в ведении журнала отладки.

Импортирует две функции с именами api и databaseSetup, которые мы создали в другом месте репозитория, которые дают нам экземпляры базы данных и Express API (шокирует).

Определяет функцию с именем normalizePort, которая изо всех сил старается вернуть целое число на основе своего ввода.

Давайте посмотрим, что делает эта функция databaseSetup.

источник/база данных/index.js

Импортирует debug и создает его экземпляр для хороших журналов отладки.

Импортирует path, чтобы мы могли безопасно построить путь к нашему каталогу «миграции».

Импортирует knex-db-manager, хорошую библиотеку, которую мы используем для управления «сервером базы данных», а не только одной базой данных. Мы можем использовать эту библиотеку для создания новых баз данных. Стандартная библиотека knex работает с уже существующей базой данных.

Импортирует функцию с именем setDatabase, которую мы будем использовать для создания набора функций базы данных для управления данными CRUD. Мы не будем слишком углубляться в это руководство, обратите внимание на часть 3, когда мы будем моделировать некоторую бизнес-логику.

Единственная экспортируемая функция под названием databaseSetup здесь делает многое.

Во-первых, он создает объект config, который передается в функцию databaseManagerFactory knex-db-manager, чтобы дать нам экземпляр нашего объекта менеджера базы данных, который я назвал dbManager.

Как мы узнали из src/index.js, эта функция возвращает обещание. Давайте пройдем через это.

Во-первых, используйте от dbManager до createDbOwnerIfNotExist, чтобы убедиться, что у нас есть необходимые разрешения для создания новых баз данных.

Затем получите экземпляр самой базы данных с knexInstance (который основан на имени базы данных, переданном в эту функцию и установленном в объекте config).

Если это первый запуск API, и мы никогда ранее не создавали базу данных, то что произойдет? Нам нужно проверить этот случай, а затем создать базу данных, если это необходимо. Лучший способ, который я нашел для этого, — просто отправить необработанный запрос в базу данных и посмотреть, выдаст ли он ошибку или нет.

Затем мы выполняем миграции, а затем последним шагом в нашей цепочке обещаний является создание небольшого объекта, который включает в себя manager (который используется в наборе тестов) и database (который сам по себе является объектом, реализующим набор определенных функций). операции CRUD, которые использует API, о которых мы поговорим в следующем руководстве).

src/api.js

Здесь красиво и просто.

Мы экспортируем эту функцию с именем api, которая принимает объект базы данных в качестве входных данных. Во-первых, он создает экземпляр сервера express. Затем он передает этот экземпляр server во все эти промежуточные функции одну за другой. Он также передает объект database в нашу промежуточную функцию storage. Наконец, верните этот сервер express.

src/промежуточное ПО/index.js

Импортирует debug и создает его экземпляр для хороших журналов отладки, которые мы будем использовать в нашем обработчике ошибок.

Импортирует express, который мы будем использовать для извлечения промежуточной функции json.

Импортирует morgan — удобную библиотеку для регистрации HTTP-запросов.

Импортирует cors, библиотеку, которая реализует промежуточное ПО, обеспечивающее поддержку CORS для API.

Функции cors и encoding достаточно просты. Они принимают экспресс-API в качестве входных данных и применяют промежуточное ПО CORS и JSON. Это позволяет нашему API поддерживать CORS и правильно отвечать на запросы JSON (с ответами JSON).

Функция logging настраивает наш импорт morgan. По сути, мы не заботимся о протоколировании HTTP-запросов при выполнении тестов. При локальной разработке распечатайте несколько хороших простых журналов с цветовой кодировкой, а в любой другой среде распечатайте более подробные журналы.

Функция storage принимает api и db в качестве входных данных и регистрирует промежуточную функцию, которая устанавливает «локальное» значение, которое мы определяем на месте, называемое db, устанавливая его в db, которое было передано в функцию. Затем он вызывает next(), чтобы продолжить цепочку промежуточного программного обеспечения. Это имеет решающее значение для операционной целостности нашего API, поскольку теперь у нас есть база данных, которая живет в каждом запросе. Нам не нужен какой-то глобальный объект базы данных во всей нашей кодовой базе, для каждого жизненного цикла запроса мы можем извлечь его прямо из запроса. Это позволяет нам установить конкретную базу данных (или фиктивный объект базы данных...) при запуске приложения в dev, staging, production или тестах.

Функция errors регистрирует промежуточное ПО с 4 входами. Это особый синтаксис в Express, так как он означает, что это промежуточное программное обеспечение, обрабатывающее «ошибку». Любая ошибка, возникающая в приложении, будет обнаружена этим промежуточным программным обеспечением. Здесь у нас есть некоторая логика, которая создает определенные сообщения об ошибках в зависимости от типа ошибки (была ли она ожидаема и обнаружена логикой приложения или какая-то другая неизвестная ошибка) и в какой среде мы работаем. Наконец, это выводит ошибку на консоль через debug.

src/маршрутизатор/index.js

Последняя часть нашей логики запуска API. Экспортированная функция router просто регистрирует пару маршрутов с именами / и /notes. В этом руководстве мы увидим, как строится корневой маршрут /. Мы рассмотрим наши заметки CRUD в следующий раз.

Функция обработчика root для нашего маршрута / принимает экземпляр экспресс-API в качестве параметра. Давай проверим это.

Корневой маршрут

Наконец, наше приложение настроено и готово к обработке запросов. Давайте посмотрим, как настраивается и обрабатывается корневой маршрут GET /.

src/маршрутизатор/root.js

Импортирует функцию Router из express, чтобы мы могли создать новый обработчик маршрута.

Импортирует функцию с именем getRoot из нашего корневого контроллера.

Наша экспортированная функция root принимает в качестве входных данных экспресс-API. Он создает новый экземпляр Router, затем регистрирует метод .get для маршрута /, вызывая getRoot(api) в качестве обработчика.

src/контроллеры/root.js

Импортирует child_process, что позволяет нам выполнять команды в базовой системе.

Импортирует express-list-endpoints, пакет, который принимает экспресс-API и выводит объект, содержащий все зарегистрированные маршруты.

Создает переменную version формы ( version из package.json , затем + , затем короткий хэш git из коммита, который в данный момент извлечен).

Экспортированная функция getRoot здесь возвращает обработчик, который отправляет объект ответа 200, содержащий некоторую информацию об API.

Ключи ответа JSON включают в себя:

  • «👋» со значением «🌎» (потому что смайлики — это весело)
  • 'name', со значением названия проекта из package.json
  • 'environment' со значением переменной окружения NODE_ENV
  • 'version' со значением той переменной version, которую мы построили выше
  • 'endpoints' со значением объекта, который express-list-endpoints выплевывает

Например, это выглядит примерно так:

{ 
  "👋": "🌎",
  "name": "decent-api",
  "environment": "development",
  "version": "0.0.0+d314f03",
  "endpoints": [
    {
      "path": "/",
      "methods": [
        "GET"
      ]
    }
  ]
}

Прохладный! Итак, если этот API работает на вашем локальном компьютере с использованием порта по умолчанию, то, если вы нажмете http://localhost:3000 в своем браузере, вы увидите приведенный выше вывод (Firefox даже отформатирует JSON для вас).

Давайте напишем несколько тестов.

Тестирование

Ни один хороший программный проект не обходится без набора тестов. Мы расскажем, как настроить набор тестов, а затем напишем несколько интеграционных тестов для нашей корневой конечной точки.

Когда мы вызываем mocha через наш скрипт test ( yarn test), он просто выполняет любые файлы, оканчивающиеся на .spec.js, которые находятся в корне нашего каталога test. Итак, мы создадим там index.spec.js, который запустит остальные наши тесты.

При выполнении тестов убедитесь, что ваш локальный сервер базы данных работает через docker-compose up.

тест/index.spec.js

Это просто. Просто импортируйте наши тесты integration (через функцию с именем integration), а затем вызовите ее.

тест/интеграция/index.spec.js

Импортирует dotenv и configure на случай, если мы не используем значения переменных среды по умолчанию.

Импортирует функцию root, которая является нашим корневым интеграционным тестом. Также импортирует функцию интеграционного тестирования notes, но мы рассмотрим это в следующем руководстве.

Здесь мы настраиваем архитектуру для интеграционных тестов. Экспортированная функция integration заключает все в блок describe под названием «интеграция». Затем мы определяем пару «частных» переменных с именами _testDbManager и _testApi, которые будут содержать экземпляры менеджера сервера базы данных и экспресс-API.

Блок before, который вызывается перед выполнением любых тестов в текущем блоке describe, вызывает databaseSetup с именем "тест" (из переменной среды NODE_ENV). Это создаст новую базу данных с именем «test», а затем вернет данные менеджера базы данных в разрешенное обещание. Мы возьмем manager и сохраним его в _testDbManager, затем передадим database в api(), чтобы создать новый экземпляр нашего Express API, и сохраним его в _testApi.

Мы также создали блок after (который выполняется после завершения выполнения всех тестов в блоке describe). Здесь мы используем наш менеджер баз данных, чтобы удалить «тестовую» базу данных, а затем закрываем соединение с локальным сервером.

Здесь важно понимать, что мы вообще не запускаем HTTP-сервер, как в src/index.js. У нас просто есть экземпляр Express API, в который мы вручную передаем запросы (см. следующий раздел).

Между нашими блоками before и after мы вызываем нашу импортированную функцию root, которая в качестве входных данных принимает функцию, возвращающую наш экземпляр _testApi, и «маршрут» для этого теста (/).

Давайте посмотрим, что делает эта функция root.

тест/интеграция/root.js

Импортирует chai и chai-http, затем настраивает их, чтобы мы могли использовать chai для тестирования наших запросов API.

Экспортированная функция root принимает в качестве входных данных функцию с именем api и файл route. Он заключен в блок describe с именем ${route} route tests, внутри которого находится еще один блок describe с именем GET ${route}. Поскольку наш корневой маршрут поддерживает только метод GET, здесь нет других блоков describe. Если бы мы разрешили пользователям переходить от POST к /, то у нас был бы еще один блок describe в качестве брата нашего блока GET ${route} под названием POST ${route}.

Сами тесты простые. Сначала мы создаем переменную с именем response, затем у нас есть блок before, который вызывается перед запуском любых тестов. Этот блок before использует chai.request и экземпляр нашего API (который мы получаем, вызывая переданную функцию api) для вызова GET / (через .get(route)), а затем сохраняет ответ в переменной response.

Затем мы определяем некоторые тесты, используя блоки it, чтобы проверять различные вещи в этом объекте ответа. Он должен иметь код состояния 200. Это должен быть объект. Он должен иметь смайлики «привет, мир». Он должен возвращать имя проекта в виде строки. Он должен вернуть окружающую среду. Он должен вернуть версию. Он должен вернуть массив маршрутов.

integration
  / route tests
    GET /
      ✓ should have 200 status
      ✓ should return an object
      ✓ should return hello world emojis
      ✓ should return project name
      ✓ should return environment
      ✓ should return version
      ✓ should have a list of endpoints
7 passing (743ms)

Вывод

В этом посте мы узнали, как загрузить Express API, используя модульную многоразовую архитектуру, которая разделяет базу данных, Express API и HTTP-сервер.

Мы создали обработчик корневого маршрута и заставили его возвращать различную информацию о конфигурации самого API.

Наконец, мы создали набор тестов, который мы сможем легко добавлять по мере создания бизнес-логики, моделей, маршрутов и контроллеров.

Присоединяйтесь ко мне в следующем посте, где мы рассмотрим, как создать CRUD вокруг концепции «заметок». Или просто посмотрите на код прямо сейчас.

Первоначально опубликовано на https://blog.decentlabs.io 6 ноября 2019 г.