Pagination

To create data filtering on the order-api service, we will expose a new endpoint that retrieves the orders through a GET method. Before we start coding, let's first change the openapi.yml file and add this new operation there:

/store/orders:
get:
tags:
- store
summary: Returns orders
description: Returns the orders
operationId: getOrder
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
'401':
$ref: '#/components/responses/UnauthorizedError'
security:
- bearerAuth: []

The full openapi.yml file is as follows:

 openapi: 3.0.0

servers:
# Added by API Auto Mocking Plugin
- description: SwaggerHub API Auto Mocking
url: https://virtserver.swaggerhub.com/biharck/hands-on/1.0.0
- description: The server description
url: https://localhost:3000/hands-on-store/1.0.0
info:
description: |
This is a sample store server. You can find
out more about Swagger at
[http://Swagger.io](http://Swagger.io)
version: "1.0.0"
title: Swagger store
termsOfService: 'http://Swagger.io/terms/'
contact:
email: [email protected]
license:
name: Apache 2.0
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
tags:
- name: store
description: Access to store orders
- name: user
description: Operations about user
externalDocs:
description: Find out more about our store
url: 'http://Swagger.io'
paths:
/store/inventory:
get:
tags:
- store
summary: Returns user inventories from the store
description: Returns a map of status codes to quantities
operationId: getInventory
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
'401':
$ref: '#/components/responses/UnauthorizedError'
security:
- bearerAuth: []
/store/orders:
get:
tags:
- store
summary: Returns orders
description: Returns the orders
operationId: getOrder
responses:
'200':
description: successful operation
content:
application/json:
schema:
type: object
additionalProperties:
type: integer
format: int32
'401':
$ref: '#/components/responses/UnauthorizedError'
security:
- bearerAuth: []
post:
tags:
- store
summary: Place an order for a user
operationId: placeOrder
responses:
'201':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid Order
'401':
$ref: '#/components/responses/UnauthorizedError'
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
description: order placed for purchasing the user
required: true
'/store/orders/{orderId}':
get:
tags:
- store
summary: Find purchase order by ID
description: >-
For valid response try integer IDs with value >= 1 and <= 10. Other
values will generated exceptions
operationId: getOrderById
parameters:
- name: orderId
in: path
description: ID of user that needs to be fetched
required: true
schema:
type: integer
format: int64
minimum: 1
maximum: 10
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/Order'
application/xml:
schema:
$ref: '#/components/schemas/Order'
'400':
description: Invalid ID supplied
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
description: Order not found
security:
- bearerAuth: []
delete:
tags:
- store
summary: Delete purchase order by ID
description: >-
For valid response try integer IDs with positive integer value.
Negative or non-integer values will generate API errors
operationId: deleteOrder
parameters:
- name: orderId
in: path
description: ID of the order that needs to be deleted
required: true
schema:
type: integer
format: int64
minimum: 1
responses:
'400':
description: Invalid ID supplied
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
description: Order not found
security:
- bearerAuth: []
/users:
post:
tags:
- user
summary: Create user
description: This can only be done by the logged in user.
operationId: createUser
responses:
'201':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
'401':
$ref: '#/components/responses/UnauthorizedError'
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
description: Created user object
required: true
/users/login:
get:
tags:
- user
summary: Logs user into the system
operationId: loginUser
parameters:
- name: username
in: query
description: The user name for login
required: true
schema:
type: string
- name: password
in: query
description: The password for login in clear text
required: true
schema:
type: string
responses:
'200':
description: successful operation
headers:
X-Rate-Limit:
description: calls per hour allowed by the user
schema:
type: integer
format: int32
X-Expires-After:
description: date in UTC when token expires
schema:
type: string
format: date-time
content:
application/json:
schema:
type: string
application/xml:
schema:
type: string
'400':
description: Invalid username/password supplied
/users/logout:
get:
tags:
- user
summary: Logs out current logged in user session
operationId: logoutUser
responses:
default:
description: successful operation
'/users/{username}':
get:
tags:
- user
summary: Get user by user name
operationId: getUserByName
parameters:
- name: username
in: path
description: The name that needs to be fetched. Use user1 for testing.
required: true
schema:
type: string
responses:
'200':
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/User'
application/xml:
schema:
$ref: '#/components/schemas/User'
'400':
description: Invalid username supplied
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
description: User not found
security:
- bearerAuth: []
patch:
tags:
- user
summary: Updated user
description: This can only be done by the logged in user.
operationId: updateUser
parameters:
- name: username
in: path
description: name that need to be updated
required: true
schema:
type: string
responses:
'204':
description: successful operation
'400':
description: Invalid user supplied
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
description: User not found
security:
- bearerAuth: []
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
description: Updated user object
required: true
delete:
tags:
- user
summary: Delete user
description: This can only be done by the logged in user.
operationId: deleteUser
parameters:
- name: username
in: path
description: The name that needs to be deleted
required: true
schema:
type: string
responses:
'204':
description: successful operation
'400':
description: Invalid username supplied
'401':
$ref: '#/components/responses/UnauthorizedError'
'404':
description: User not found
security:
- bearerAuth: []
externalDocs:
description: Find out more about Swagger
url: 'http://Swagger.io'
components:
responses:
UnauthorizedError:
description: Access token is missing or invalid
schemas:
Order:
type: object
properties:
id:
type: integer
format: int64
userId:
type: integer
format: int64
quantity:
type: integer
format: int32
shipDate:
type: string
format: date-time
status:
type: string
description: Order Status
enum:
- placed
- approved
- delivered
complete:
type: boolean
default: false
xml:
name: Order
User:
type: object
properties:
id:
type: integer
format: int64
username:
type: string
firstName:
type: string
lastName:
type: string
email:
type: string
password:
type: string
phone:
type: string
userStatus:
type: integer
format: int32
description: User Status
xml:
name: User

securitySchemes:
bearerAuth: # arbitrary name for the security scheme
type: http
scheme: bearer
bearerFormat: JWT # optional, arbitrary value for documentation purposes

Now, we can create our test on test/routes/order.spec.ts:

  it('should return all orders so far', async () => {
return chai
.request(app)
.get(`/store/orders`)
.then(res => {
expect(res.status).to.be.equal(200)
expect(res.body.length).to.be.equal(1)
})
})

The whole file should look as follows:

'use strict'

import * as chai from 'chai'
import chaiHttp = require('chai-http')
import 'mocha'
import app from '../../src/app'
import Order from '../../src/models/order'
import { OrderStatus } from '../../src/model/orderStatus'

chai.use(chaiHttp)

const expect = chai.expect

const order: Order = {
// generic random value from 1 to 100 only for tests so far
id: 1,
userId: 20,
quantity: 1,
shipDate: new Date(),
status: OrderStatus.Placed,
complete: false,
}

describe('userRoute', () => {
it('should respond with HTTP 404 status because there is no order', async () => {
return chai
.request(app)
.get(`/store/orders/${order.id}`)
.then(res => {
expect(res.status).to.be.equal(404)
})
})
it('should create a new order and retrieve it back', async () => {
return chai
.request(app)
.post('/store/orders')
.send(order)
.then(res => {
expect(res.status).to.be.equal(201)
expect(res.body.userId).to.be.equal(order.userId)
expect(res.body.complete).to.be.equal(false)
order.id = res.body.id
})
})
it('should return the order created on the step before', async () => {
return chai
.request(app)
.get(`/store/orders/${order.id}`)
.then(res => {
expect(res.status).to.be.equal(200)
expect(res.body.id).to.be.equal(order.id)
expect(res.body.status).to.be.equal(order.status)
})
})
it('should return all orders so far', async () => {
return chai
.request(app)
.get(`/store/orders`)
.then(res => {
expect(res.status).to.be.equal(200)
expect(res.body.length).to.be.equal(1)
})
})
it('should return the inventory for all users', async () => {
return chai
.request(app)
.get(`/store/inventory`)
.then(res => {
expect(res.status).to.be.equal(200)
expect(res.body[20].length).to.be.equal(1)
})
})
it('should remove an existing order', async () => {
return chai
.request(app)
.del(`/store/orders/${order.id}`)
.then(res => {
expect(res.status).to.be.equal(204)
})
})
it('should return 404 when it is trying to remove an order because the order does not exist', async () => {
return chai
.request(app)
.del(`/store/orders/${order.id}`)
.then(res => {
expect(res.status).to.be.equal(404)
})
})
})

Now, we can implement the GET method, including the pagination rule on the src/controllers/order.ts file:

import { NextFunction, Request, Response } from 'express'
import * as _ from 'lodash'
import { default as Order } from '../models/order'
import { OrderStatus } from '../models/orderStatus'

let orders: Array<Order> = []

export let getOrder = (req: Request, res: Response, next: NextFunction) => {
const id = req.params.id
const order = orders.find(obj => obj.id === Number(id))
const httpStatusCode = order ? 200 : 404
return res.status(httpStatusCode).send(order)
}

export let getAllOrders = (req: Request, res: Response, next: NextFunction) => {
return res.status(200).send(orders)
}

export let addOrder = (req: Request, res: Response, next: NextFunction) => {
const order: Order = {
// generic random value from 1 to 100 only for tests so far
id: Math.floor(Math.random() * 100) + 1,
userId: req.body.userId,
quantity: req.body.quantity,
shipDate: req.body.shipDate,
status: OrderStatus.Placed,
complete: false,
}
orders.push(order)
return res.status(201).send(order)
}

export let removeOrder = (req: Request, res: Response, next: NextFunction) => {
const id = Number(req.params.id)
const orderIndex = orders.findIndex(item => item.id === id)

if (orderIndex === -1) {
return res.status(404).send()
}

orders = orders.filter(item => item.id !== id)

return res.status(204).send()
}

export let getInventory = (req: Request, res: Response, next: NextFunction) => {
const status = req.query.status
let inventoryOrders = orders
if (status) {
inventoryOrders = inventoryOrders.filter(item => item.status === status)
}

const grouppedOrders = _.groupBy(inventoryOrders, 'userId')
return res.status(200).send(grouppedOrders)
}

We can also include the route on src/routes/order.ts:

import * as orderController from '../controllers/order'

export class OrderRoute {
public routes(app): void {
app.route('/store/inventory').get(orderController.getInventory)
app.route('/store/orders').post(orderController.addOrder)
app.route('/store/orders').get(orderController.getAllOrders)
app.route('/store/orders/:id').get(orderController.getOrder)
app.route('/store/orders/:id').delete(orderController.removeOrder)
}
}

Now, if you start the application and add some order and then hit the new GET method, you should be able to see all of the orders:

Example of new GET operation in action

Now that this has been implemented, the next step is to add a new test that will use the limit and offset strategies.

On test/routes/order.ts, add a new test:

it('should not return orders because offset is higher than the size of the orders array', async () => {
return chai
.request(app)
.get(`/store/orders?offset=2&limit=2`)
.then(res => {
expect(res.status).to.be.equal(200)
expect(res.body.length).to.be.equal(0)
})
})

And in the src/controllers/orders.ts file, update the getAllOrders function with the new query strings:

it('should not return orders because offset is higher than the size of the orders array', async () => {
return chai
.request(app)
.get(`/store/orders?offset=2&limit=2`)
.then(res => {
expect(res.status).to.be.equal(200)
expect(res.body.length).to.be.equal(0)
})
})

For that, we are using loadash again, which makes our lives easier. 

After running the tests, you should see that they all pass:

$ npm run test

The output should be as follows:

baseRoute
should respond with HTTP 200 status (48ms)
should respond with success message

userRoute
should respond with HTTP 404 status because there is no order
should create a new order and retrieve it back
should return the order created on the step before
should return all orders so far
should not return orders because offset is higher than the size of the orders array
should return the inventory for all users
should remove an existing order
should return 404 when it is trying to remove an order because the order does not exist

userRoute
should respond with HTTP 404 status because there is no user
should create a new user and retrieve it back
should return the user created on the step before
should updated the user John
should return the user updated on the step before
should return 404 because the user does not exist
should remove an existent user
should return 404 when it is trying to remove an user because the user does not exist


18 passing (138ms)

Now, if you start the application, create a few orders, and use the pagination strategy, everything should work fine:

An example with limit and offset when getting orders

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.217.144.32