API на Nodejs, GraphQL, MongoDB, Hapi и Swagger ч. 2

8 мин. на чтение

В первой части мы настроили вполне себе современный REST API – использовали nodejs, mongodb и бэкбон hapi, пришло время сделать наш API сервер еще более современным. Наверное, вы и читать эту статью начали только ради GraphQL. Если с GraphQL все более-менее понятно, то последней технологией станет авто документирование нашего API средствами Swagger. Но обо все по-порядку.

GraphQL

Собственно, почему GraphQL? Некоторые наши коллеги представляют что GraphQL пришел на смену REST API, как тот пришел в свое время чтобы сменить SOAP. В общем-то, нет никаких особых причин в этом утверждении и оба архитектурных стиля заслуживают на существование в 2018-м году.

GraphQL как минимум способен решить две болевые точки REST API:

  • избыточность данных – не всегда нам нужны все поля, которые возвращает метод API;
  • недостаточность данных – не всегда нам достаточно данных, которые возвращает один метод API и приходится вызывать еще один.

Вводные данные вы без труда найдете в сети, так что предлагаю вернуться к нашему API серверу.

Чтобы добавить GraphQL, нужно установить несколько дополнительных пакетов:

yarn add graphql apollo-server-hapi

graphql – основной пакет для работы с новой архитектурой, а apollo-server-hapi обеспечивает связь с нашим бекбоном.

Теперь в корне нашего проекта сделаем новую папку graphql, а в ней файл PaintingType.js следующего содержания:

const graphql = require('graphql');

const { GraphQLObjectType, GraphQLString } = graphql;

const PaintingType = new GraphQLObjectType({
	
	name: 'Painting',

	fields: () => ({
		id: { type: GraphQLString },
		name: { type: GraphQLString },
		url: { type: GraphQLString },
		techniques: { type: GraphQLString }
	})
});

module.exports = PaintingType;

Сначала мы подключили модуль graphql, потом декомпозировали объект graphql. Строка 3 у нас аналогична такой записи:

const GraphQLObjectType = graphql.GraphQLObjectType;

const GraphQLString = graphql.GraphQLString;

Далее мы создали новый объект GraphQLObjectType, Для всех элементов объекта в GraphQL должен быть определен тип, т.к. GraphQL – язык со статической типизацией. Сейчас все наши элементы имеют тип GraphQLString.

Это был наш главный запрос для обработки запросов о Painting. Теперь нужно встроить его в наш сервер. Создаем еще один файлик в папке graphql с названием schema.js

const graphql = require('graphql');

const PaintingType = require('./PaintingType');

const {
	GraphQLObjectType,
	GraphQLString,
	GraphQLSchema
} = graphql;

const RootQuery = new GraphQLObjectType({
	name: 'RootQueryType',

	fields: {
		painting: {
			type: PaintingType,
			args: { id: { type: GraphQLString } },
			resolve(parent, args){
				// дополнительная логика
			}
		}
	}	
});

module.exports = new GraphQLSchema({
	query: RootQuery
});

Обратите внимание, что теперь наш код запроса чуток более запутан, чем без GraphQL. У нас теперь painting – это поле, у которого тип из нашей схемы PaintingType, а его id – агрумент, т.к. именно по этому полю мы будем делать выборки из Монги.

Ну и в конце у нас есть функция resolve(), которая обрабатывает все наши данные. Давайте для примера посмотрим на формат запроса GraphQL:

{
  painting(id: "5b3fc02f3add950b79393ea6") {
    name,
    url
  }
}

А в самом конце у нас Компонент экспортирует данные.

Вернемся в index.js и подключим всю эту красоту. Нам нужно зарегистрировать при старте сервера модули для работы с GraphQL, давайте сделаем это:

await server.register({
		plugin: graphiqlHapi,
		options: {
			path: '/graphiql',
			graphiqlOptions: {
				endpointURL: '/graphql'
			},
			route: {
				cors: true
			}
		}

	});
	await server.register({
		plugin: graphqlHapi,
		options: {
			path: '/graphql',
			graphqlOptions: {
				schema
			},
			route: {
				cors: true
			}

		}
	});

Обратите внимание, что модулей у нас два и graphiql это не graphql. Он представляет собой браузерную IDE, которая дает возможности проверять свои graphql query и какой нам возвращается ответ.

Сохраняем это все и давайте откроем адрес: http://localhost:4000/graphiql

Все прекрасно работает:

Все конечно круто, но почему мы до сих пор получаем пустые ответы? Варианта тут два:

  • у нас нет painting с id = 2;
  • а даже если у нас есть и такой id, то мы все равно еще не получаем данные из самой Монги. Функция resolve() у нас все еще пустая, давайте это исправим.

В schema.js добавим следующее:

const Painting = require('./../models/Painting');

И в функцию resolve

return Painting.findById(args.id)

Все вместе должно получиться так:

const graphql = require('graphql');

const PaintingType = require('./PaintingType');

const Painting = require('./../models/Painting');

const {
	GraphQLObjectType,
	GraphQLString,
	GraphQLSchema
} = graphql;

const RootQuery = new GraphQLObjectType({
	name: 'RootQueryType',

	fields: {
		painting: {
			type: PaintingType,
			args: { id: { type: GraphQLString } },
			resolve(parent, args){
				return Painting.findById(args.id)
			}
		}
	}	
});

module.exports = new GraphQLSchema({
	query: RootQuery
});

Снова пробуем graphiql:

Все почти круто, у нас тут видите ли немного неисправна модель, давайте исправим это. Собственно нужно просто изменить тип со [String] на String.

const PaintingSchema = new Schema({
	name: String,
	url: String,
	technique: String
});

Теперь все работает! Можно добавить еще пару объектов через Postman и поиграться с выводом необходимых полей.

Swagger

От крутого современного API нас отделяет только автодокументация средствами Swagger. Давайте добавим ее. Установка очень простая, но результат отличный – вы практически избавляетесь от необходимости создавать какие-то документации вне кода, забудьте про приватные сайты, вердовские документы и прочее.

Для начала установим пакеты:

yarn add hapi-swagger inert vision

Подключим плагины, воспользовавшись документацией с Гитхаба для пакета happi-swagger:

const swaggerOptions = {
        info: {
                title: 'Documentation about API for Painting ',
                version: Pack.version,
            },
        };
    
    await server.register([
        Inert,
        Vision,
        {
            plugin: HapiSwagger,
            options: swaggerOptions
        }
    ]);

Сохраняем, заходим на http://localhost:4000/documentation

Все работает, но осталось добавить документацию к нашим endpoints. В секции server.route() добавим пару полей:

{
			method: 'GET',
			path: '/api/v1/paintings',
			config: {
				description: 'Get Painting',
				tags: ['api', 'v1', 'someone-tag']
			},
			handler: (req, reply) => {
				return Painting.find();
			}
		},
		{
			method: 'POST',
			path: '/api/v1/paintings',
			config: {
				description: 'Add new Painting',
				tags: ['api', 'painting']
			},
			handler: (req, reply) => {
				const { name, url, techniques } = req.payload;
				const painting = new Painting({
					name,
					url,
					techniques
				});
			return painting.save();
			}
		}

Сохраняем, проверяем:

Вот теперь все круто. Всем отличного кодинга.

1 Комментарий

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте как обрабатываются ваши данные комментариев.