- Antes de avançarmos, vamos cuidar de um último ponto da nossa rota, a paginação. É interessante que possamos obter as categorias parcialmente, portanto vamos implementar a paginação para permitir isso. Comece incluindo os parâmetro de query page e perPage e então garantindo que eles serão números inteiros válidos para trabalharmos com páginas:
- Obs.: Repare que o limite será de 10 itens por página e a página será a 1ª se os parâmetros não forem especificados na query.
- Obs².: Em SQL utilizamos o OFFSET para “pular” alguns recursos, por isso o offset deve ser a página - 1 vezes o limite, pois será a quantidade de linhas a ignorar.
// src/controllers/categoriesController.ts
// ...
index: async (req: Request, res: Response) => {
const { page, perPage } = req.query
const perPageNumber = typeof perPage === 'string' && parseInt(perPage, 10) > 0
? parseInt(perPage, 10)
: 10
const pageNumber = typeof page === 'string' && parseInt(page, 10) > 0
? parseInt(page, 10)
: 1
const offset = (pageNumber - 1) * perPageNumber
try {
// ...
- Agora vamos atualizar a query ao banco de dados com o sequelize utilizando os parâmetros para realizar a paginação e retornar nossos dados em um objeto mais organizado:
- Obs.: Repare que alteramos o método findAll para findAndCountAll. Isso é porque o sequelize oferece esse método auxiliar para facilitar a implementação da paginação já que ao paginar os resultados é comum enviar também a contagem total de resultados que batem com a query. Assim evitamos de chamar o método count separadamente.
// src/controllers/categoriesController.ts
// ...
try {
const { count, rows } = await Category.findAndCountAll({
attributes: ['id', 'name', 'position'],
order: [['position', 'ASC']],
limit: perPage,
offset
})
return res.json({
categories: rows,
page: pageNumber,
perPage: limit,
total: count
})
} catch (err) {
// ...
- Já é possível testar o endpoint e ver que ele funciona como deveria com a paginação.
- Por fim, nosso método no controlador cresceu bastante em tamanho e, se continuarmos adicionando métodos e funcionalidades, nosso controlador pode assumir responsabilidades demais. Para corrigir isso podemos criar uma pasta services dentro de src e então criar o arquivo categoryService.ts. E então moveremos a maior parte de nossa lógica de consulta para ele, deixando-o assim:
// src/services/categoryService.ts
import { Category } from '../models'
export const categoryService = {
findAllPaginated: async (page: number, perPage: number) => {
const offset = (page - 1) * perPage
const { count, rows } = await Category.findAndCountAll({
attributes: ['id', 'name', 'position'],
order: [['position', 'ASC']],
limit: perPage,
offset
})
return {
categories: rows,
page,
perPage,
total: count
}
}
}
- Além dos services, podemos criar a pasta helpers e, dentro dela, um arquivo getPaginationParams.ts e mover para ele a responsabilidade de obter os parâmetros de paginação de uma query. Assim ainda poderemos utilizá-lo em outros momentos sem repetir código:
// src/helpers/getPaginationParams.ts
export function getPaginationParams(query: any): [page: number, perPage: number] {
const { page, perPage } = query
const perPageNumber = typeof perPage === 'string' && parseInt(perPage, 10) > 0
? parseInt(perPage, 10)
: 10
const pageNumber = typeof page === 'string' && parseInt(page, 10) > 0
? parseInt(page, 10)
: 1
return [pageNumber, perPageNumber]
}
- Agora nosso controlador deve estar mais ou menos assim:
// src/controllers/categories-controller.ts
import { Request, Response } from 'express'
import { getPaginationParams } from '../helpers/getPaginationParams'
import { categoryService } from '../services/categoryService'
const categoriesController = {
index: async (req: Request, res: Response) => {
const [page, perPage] = getPaginationParams(req.query)
try {
const paginatedCategories = await categoryService.findAllPaginated(page, perPage)
return res.json(paginatedCategories)
} catch (err) {
if (err instanceof Error) {
return res.status(400).json({ message: err.message })
}
}
}
}
export { categoriesController }
- Com isso o deixamos mais organizado e nosso endpoint continua funcionando corretamente.