Рубрики
Без рубрики

QueryQL: Легко добавить фильтрацию, сортировку и страсть в свой API Node.js REST

Когда вы впервые начнете создавать API, вы, вероятно, жесткий код в нескольких вещах, таких как сортировка и P … Tagged с JavaScript, Node, API.

Когда вы впервые начнете создавать API, вы, вероятно, жестко код в нескольких вещах, таких как сортировка и страсть. Может быть, что -то подобное Экспресс Маршрут выглядит для вас знакомо (он использует Knex Чтобы запросить базу данных):

app.get('/images', async (req, res, next) => {
  const images = await knex('images')
    .orderBy('created_at', 'desc')
    .limit(20)

  res.send({ images })
})

Как только вы пойдете немного дальше, вы начинаете добавлять пользовательский интерфейс для контроля сортировки и страниц – и, возможно, что -то вроде фильтра «статуса» для удобства. Те, кто ранее кодированные значения теперь должны быть приняты через строку запроса, и ваш код начинает расти:

app.get('/images', async (req, res, next) => {
  const orderByColumn = req.query.order_by_column || 'created_at'
  const orderByDirection = req.query.order_by_direction || 'desc'
  const page = req.query.page || 1
  const limit = req.query.limit || 20
  const where = {}

  if (req.query.status) {
    where.status = req.query.status
  }

  const images = await knex('images')
    .where(where)
    .orderBy(orderByColumn, orderByDirection)
    .limit(limit)
    .offset((page - 1) * limit)

  res.send({ images })
})

Это еще не громоздко, но также отсутствует какая -либо валидация значений строки запроса. Что, если не существует order_by_column передается? Или предел из FOOBAR ? Запрос взрывается.

Возможно, что еще хуже, если А предел из 1000000 (один миллион) блокирует вашу базу данных? Или Статус Указано, что нельзя допустить? Ни один из них не обязательно не может решить самостоятельно, но это становится важной рутиной для управления по всему API. Маршруты становятся раздутыми с шаблоном, консистенцией в именовании и по умолчанию сломаются, и это становится уточнением, чтобы добавить новые конечные точки.

Теперь, если вы предприимчивый разработчик, возможно, вы извлекаете часть этого в промежуточное программное обеспечение, библиотеки или объекты службы, которыми легче поделиться и поддерживать. Это отличное начало. Но вы действительно хотите провести время на что -то так далекое от фактической цели вашего API?

Представление QueryQL

Queryql Решает все это.

Вместо хрупкого, раздутого маршрута мы оказались выше, QueryQl позволяет определить фильтрацию, сортировку и страсть в отдельном Querier учебный класс:

const QueryQL = require('@truepic/queryql')

class ImageQuerier extends QueryQL {
  defineSchema(schema) {
    schema.filter('status', '=')
    schema.sort('name')
    schema.sort('created_at')
    schema.page()
  }

  defineValidation(schema) {
    return {
      'filter:status[=]': schema.string().valid('open', 'closed'),
      'page:size': schema.number().max(100),
    }
  }

  get defaultSort() {
    return {
      created_at: 'desc',
    }
  }
}

Наш маршрут может затем использовать этот класс Querier, чтобы значительно очистить себя:

app.get('/images', async (req, res, next) => {
  const querier = new ImageQuerier(req.query, knex('images'))

  const images = await querier.run()

  res.send({ images })
})

Как это работает

Давайте поговорим о том, что здесь происходит, начиная с использования класса Querier в нашем маршруте:

const querier = new ImageQuerier(req.query, knex('images'))

Первый параметр, req.query , это проанализированная строка запроса. Мы вытаскиваем это из Express ‘ req Здесь, но это может прийти откуда угодно. QueryQL не зависит от какой -либо конкретной веб -структуры Node.js. Это работает так же хорошо с экспрессом, как и с Коа или Hapi Анкет

Однако конкретный формат строки запроса важен. (В конце концов, «QL» в QueryQL означает «язык запросов»). Вот простой пример того, как он может искать наш маршрут:

/images?filter[status]=open&sort=name&page[size]=10

И вот он проанализирован:

{
  filter: {
    status: 'open',
  },
  sort: 'name',
  page: {
    size: 10,
  },
}

Есть много, чтобы распаковать конкретный формат строки запроса, но я оставлю это в Документация перечислять на данный момент.

Второй параметр, Knex ('Images') , является началом запроса Knex, который QueryQl применит запрошенную фильтрацию, сортировку и страницу. KNEX поддерживается вне коробки (и ORM, построенные на вершине, как Apection.js ), но адаптеры могут быть записаны для любого строителя запросов/ORM без особых усилий. (Это даже не должно быть на основе SQL.)

Давайте вернемся к самому классу Querier.

Единственная требуемая функция класса Querier – Определитешему , вот где мы белый список, что разрешено:

defineSchema(schema) {
  schema.filter('status', '=')
  schema.sort('name')
  schema.sort('created_at')
  schema.page()
}

Здесь мы белые списки

  • A Статус Фильтр с = оператор (также поддерживается множество других операторов),
  • Сортировка на имя и create_at В
  • и обеспечение лиц.

Одно это решает большинство наших проблем. Что, если не существует сортировка Поле передается? Или Страница: размер из FOOBAR ? QueryQL улавливает их – и любые другие утронутые или гнусные запросы – и возвращает элегантное, точное сообщение об ошибке задолго до того, как у него появится возможность взорвать ваш запрос базы данных.

Несмотря на то, что это обеспечивает хорошую стартовую линию защиты, вы, вероятно, все еще хотите обеспечить проверку, специфичную для вашего API. Вот где Определитевидность приходит в:

defineValidation(schema) {
  return {
    'filter:status[=]': schema.string().valid('open', 'closed'),
    'page:size': schema.number().max(100),
  }
}

Здесь мы просто

  • Ограничение Статус Фильтр до значения Open или закрыто В
  • и ограничение размера максимальной страницы 100 Анкет

схема , в этом случае, это JOI , который является валидатором, который выходит из коробки с QueryQL. Но, как и в случае с адаптерами, валидаторы могут быть написаны с использованием любой базовой библиотеки проверки без особых усилий, на случай, если вы предпочитаете что -то другое.

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

get defaultSort() {
  return {
    created_at: 'desc',
  }
}

Вот и все! (Ну, есть еще многое другое – например, как установить другие значения по умолчанию, настраивать запросы для более продвинутого использования и многое другое – но документация

Большое улучшение по сравнению с ручной фильтрацией, сортировкой и страницей страниц

Помните наш маршрут с более раннего?

app.get('/images', async (req, res, next) => {
  const orderByColumn = req.query.order_by_column || 'created_at'
  const orderByDirection = req.query.order_by_direction || 'desc'
  const page = req.query.page || 1
  const limit = req.query.limit || 20
  const where = {}

  if (req.query.status) {
    where.status = req.query.status
  }

  const images = await knex('images')
    .where(where)
    .orderBy(orderByColumn, orderByDirection)
    .limit(limit)
    .offset((page - 1) * limit)

  res.send({ images })
})

У нас был код таким же, как это в наших API в Truepic долгое время. Это сработало, конечно, но это было далеко не пуленепробиваемой и, конечно, не приятно поддерживать. Когда мы должны были сосредоточиться на гораздо более важной (и веселой) работе по созданию фото и проверки видео, мы управляли шаблоном на всех наших маршрутах.

Вот почему мы построили QueryQL, и мы рады поделиться им с сообществом в качестве проекта с открытым исходным кодом на GitHub Анкет Добавьте его в свой API node.js сегодня с npm :

$ npm install @truepic/queryql

Оригинал: “https://dev.to/jstayton/queryql-easily-add-filtering-sorting-and-pagination-to-your-node-js-rest-api-32l9”