Swagger-js, tips and tricks
This article explains step-by-step how to set up and use the Swagger Client module for your JavaScript project. It also shows examples about how to properly use it and some useful tips and tricks that may help you in your development.
About Swagger-js
Exactly as they define it in their github repository: Swagger Client is a JavaScript module that allows you to fetch, resolve, and interact with Swagger/OpenAPI documents
. Thanks to these tools the developer is able to define the API to be used in a clean manner, and ensure all the code uses the latest API version.
However the JavaScript implementation of Swagger Client has some important differences, since, unlike other programming languages, it doesn't use Swagger Codegen. Codegen is a tool that reads your API spec and then returns all the auto-generated code that you'll later may import to your project, but in JavaScript this is achieved in a quite different way.
Instead of giving you a static code, Swagger-js works by auto-generating all API request functions dynamically, that is to say, while the program is running. This can be really helpful as you won't need to regenerate your code each time you make a change in your API definition, but can also give you some trouble if you are not experienced with it. I'll later explain how to solve/avoid any issues.
Installation
NodeJS
npm install swagger-client
If your project uses React Native I recommend using swagger-client v3.5.2, as later versions won't work out of debugging mode:
[swagger-js/issues/#1397]
npm install swagger-client@3.5.2
const SwaggerClient = require('swagger-client')
Web Browser
<script src="//unpkg.com/swagger-client" type="text/javascript"></script>
How to use it
As an example I'll use the pet store demo from Swaggervar specUrl = 'http://petstore.swagger.io/v2/swagger.json' var swaggerClient = new SwaggerClient(specUrl)
After creating a new SwaggerClient object it will return a Promise. This Promise resolves when the spec fetching and the function auto-generation process is ended, returning an object with all the client information and operations. Inside it the most interesting part is the apis object, containing all the dynamically generated functions.
Here is an example of how it looks like:swaggerClient.then(client => console.log(client.apis)) ====== CONSOLE LOGS ====== apis: { pet: { addPet: [Function], updatePet: [Function], findPetsByStatus: [Function], findPetsByTags: [Function], getPetById: [Function], updatePetWithForm: [Function], deletePet: [Function], uploadFile: [Function] }, store: { getInventory: [Function], placeOrder: [Function], getOrderById: [Function], deleteOrder: [Function] }, user: { createUser: [Function], createUsersWithArrayInput: [Function], createUsersWithListInput: [Function], loginUser: [Function], logoutUser: [Function], getUserByName: [Function], updateUser: [Function], deleteUser: [Function] } }As you may see the apis object is organised by tag and operation names, both of them using the same names as defined in the API spec. After calling any operation we'll receive a Promise that will be resolved when the request is finished, returning a response object. Here is an example of how to use the client object and the possible parameters you can define:
swaggerClient.then(client => { client .apis .pet // tag name == `pet` .addPet( // operationId == `addPet` { // operation parameters Object id: 1 }, { requestBody: { // body parameters Object name: 'bobby', status: 'available' }, // this should exactly match a URL in your `servers` server: 'http://petstore.swagger.io/{apiPrefix}/', serverVariables: { apiPrefix: 'v2' } }) .then(response => { ... }) })
Response shape
response: { url, method, status, statusText, headers, // See note below regarding headers text, // The textual content body, // The body object }
Example
swaggerClient .then(({ apis }) => apis.pet.getPetById({ petId: 1 })) .then(({ body }) => console.log(body)) ====== CONSOLE LOGS ====== { id: 1, category: { id: 0, name: 'DOGS' }, name: 'doggie', photoUrls: [ 'string' ], tags: [ { id: 0, name: 'string' } ], status: 'available' }
Tips & Tricks
How to decorate Swagger auto-generated functions
Having all functions auto-generated is pretty useful, but sometimes it can be a nightmare. One of the downsides is that there is no easy way to modify them adding some custom code. For instance, imagine you want to show an animation during the API call, you would need to modify each one of the functions. To solve this I've created a little script that lets you define a decorator function that will modify every operation function.
((self, swaggerDecorator) => { typeof exports === 'object' && typeof module === 'object' ? module.exports = swaggerDecorator : self.swaggerDecorator = swaggerDecorator })(typeof self !== 'undefined' ? self : this, (SwaggerClient => { const defaultDecorator = (fnc, {}, ...args) => fnc(...args).then(({ body }) => body) return (specUrl, decorator = defaultDecorator) => new SwaggerClient(specUrl).then(({ apis }) => { Object.keys(apis).forEach(tagName => Object.entries(apis[tagName]).forEach(([functionName, fnc]) => apis[tagName][functionName] = (...args) => decorator(fnc, { tagName, functionName }, ...args))) return apis }) })(typeof SwaggerClient !== 'undefined' ? SwaggerClient : require('swagger-client')))↧ swaggerDecorator.js
swaggerDecorator(specUrl[, decorator]) : Promise<apis>
SwaggerDecorator installation
NodeJS
const swaggerDecorator = require('./swaggerDecorator')
Web Browser
<script src="//unpkg.com/swagger-client" type="text/javascript"></script> <script src="swaggerDecorator.js" type="text/javascript"></script>
Decorator function shape
const decorator = (fnc, { tagName, functionName }, ...args) => { ... Anything to execute before operation call ... // You can modify any parameter before the operation call return fnc(...args).then(response => { ... Anything to execute after operation call ... return response.body // You can change the response value }) }
SwaggerDecorator examples
By default, if no decorator function is defined it will use a simple decorator that replaces the usual response by the response body. Besides, this script also replaces the initial Promise with another one that directly returns the apis object instead of the client one.var specUrl = 'http://petstore.swagger.io/v2/swagger.json' var swaggerAPIs = swaggerDecorator(specUrl) swaggerAPIs .then(({ pet }) => pet.getPetById({ petId: 1 })) .then(body => console.log(body))
Basic test:
var decorator = (fnc, { tagName, functionName }, ...args) => { console.log('===> Before operation call') return fnc(...args).then(({ body }) => { console.log(body) console.log('===> After operation call') }) } var specUrl = 'http://petstore.swagger.io/v2/swagger.json' var swaggerAPIs = swaggerDecorator(specUrl, decorator) swaggerAPIs.then(({ pet }) => pet.getPetById({ petId: 1 })) ====== CONSOLE LOGS ====== ===> Before operation call { id: 1, category: { id: 1, name: 'woof' }, name: 'woof', photoUrls: [ '<string>', '<string>' ], tags: [ { id: 1, name: 'woof' }, { id: 1, name: 'woof' } ], status: '1' } ===> After operation call
Print debugging info:
var decorator = (fnc, { tagName, functionName }, ...args) => { console.log(`${tagName}.${functionName} => `, args) return fnc(...args).then(({ body }) => body) } var specUrl = 'http://petstore.swagger.io/v2/swagger.json' var swaggerAPIs = swaggerDecorator(specUrl, decorator) swaggerAPIs .then(({ pet }) => pet.getPetById({ petId: 1 })) .then(body => console.log(body)) ====== CONSOLE LOGS ====== pet.getPetById => [ { petId: 1 } ] { id: 1, category: { id: 0, name: 'DOGS' }, name: 'doggie', photoUrls: [ 'string' ], tags: [ { id: 0, name: 'string' } ], status: 'available' }
Set predefined parameters:
var decorator = (fnc, { tagName, functionName }, ...args) => { args[0] = args[0] || {} args[0].petId = 1 return fnc(...args).then(({ body }) => body) } var specUrl = 'http://petstore.swagger.io/v2/swagger.json' var swaggerAPIs = swaggerDecorator(specUrl, decorator) swaggerAPIs .then(({ pet }) => pet.getPetById()) // No need to define petId as it is hardcoded .then(body => console.log(body)) ====== CONSOLE LOGS ====== { id: 1, category: { id: 0, name: 'DOGS' }, name: 'doggie', photoUrls: [ 'string' ], tags: [ { id: 0, name: 'string' } ], status: 'available' }
This can be really useful if you need to specify a token for every API call
const decorator = (fnc, { tagName, functionName }, ...args) => { args[0] = args[0] || {} args[0].AuthToken = getAuthToken() return fnc(...args).then(({ body }) => body) }
Basic tips
When using Swagger-js you really need to bear in mind that every change you make in your API spec file will be immediately reflected over your project. So using the same specUrl for both development/testing and pro versions isn't a good idea. Therefore I strongly recommend creating a locked copy of the API definition for production usage, that way you can also define a different API server for each one inside the spec file, and avoid worrying about defining any variable for the server in your code. Just change the specUrl and Swagger will get the default server for you.
Another option could be converting the JSON inside the spec file to base64 and using it as a data url like so:
data:application/json;base64,abc...
Where abc... would be the base64 stringvar swaggerClient = new SwaggerClient("data:application/json;base64,abc...")