Giter Site home page Giter Site logo

koa-swagger-decorator's Introduction

koa-swagger-decorator

defined your api with simple decorators and generate api docs automatically

Important

this docs is written for V2 and it's currently under development. refer to V1 Docs for V1 version

V2 version has undergone complete refactoring, introducing break change and new APIs to provide more type-safe functionality.

  1. Installation

  2. Introduction

  3. Usage

    1. Quick Example
    2. Runnable Example
    3. Typescript Configuration
    4. Integrate with Koa
    5. Define query/path params
    6. Define body/responses params
    7. Using router.prefix
    8. Using Middlewares
  4. TODO List

Installation

npm i koa-swagger-decorator@next

Introduction

Developing your type-safe API using simple decorators and zod schema with auto-generated OpenAPI docs based on OpenAPI V3.

Usage

Quick Example

// define your api handler with @routeConfig decorator and it will generate OpenAPI Docs automatically
class UserController {
  @routeConfig({ // define your API route info using @routeConfig decorator
    method: "post",
    path: "/users",
    summary: "create a user",
    tags: ["USER"],
    operationId: "CreateUser",
  })
  @body(z.object({uid: z.string(), name: z.string(), age: z.number().min(18).optional()}))
  async CreateUser(ctx: Context, args) {
    // body params will be validated using zodSchema.parse(ctx.request.body)
    // and assigned the parsed value to args.
    console.log(args, args.body.uid, args.body.name);
    ctx.body = { message: "create", id: "123" } as ICreateUserRes;
  }
}

Runnable Example

you can refer to example dir for details.

git clone https://github.com/Cody2333/koa-swagger-decorator.git
cd koa-swagger-decorator
npm i
npm run dev

// open http://localhost:3000/api/swagger-html to execute api
// open example dir for detail codes.

Typescript Configuration

typescript is required. Please make sure compilerOptions is set correctly in tsconfig.json

// tsconfig.json
{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "esModuleInterop": true,
  }
}

Integrate with Koa

following below steps to integrate your koa application with koa-swagger-decorator

  1. Define your Request/Response with Zod Schema
// file -> ./schema/user.ts
import { z } from 'koa-swagger-decorator'
// define req/res schema
const CreateUserReq = z.object({
  uid: z.string().nonempty(),
  name: z.string().nullable().optional(),
  age: z.number().min(18).nullable(),
  operator: z.string().nonempty().optional(),
});

const CreateUserRes = z.object({
  id: z.string().nullable(),
  message: z.string().nullable(),
});

// export req/res typings
export type ICreateUserRes = z.infer<typeof CreateUserRes>;
export type ICreateUserReq = z.infer<typeof CreateUserReq>;
  1. Write your API Handler with decorator and zod schema
// file -> ./controller/user.ts
import { Context } from "koa";
import { body, responses, routeConfig } from "../../lib/decorator";
import {
  CreateUserReq,
  CreateUserRes,
} from "../schemas/user";
import { ParsedArgs, z } from "../../lib";

class UserController {
 
  @routeConfig({ // define your API route info using @routeConfig decorator
    method: "post",
    path: "/users",
    summary: "create a user",
    tags: ["USER"],
    operationId: "CreateUser",
  })
  @body(CreateUserReq)
  @responses(CreateUserRes)
  async CreateUser(ctx: Context, args: ParsedArgs<ICreateUserReq>) {
    // args is injected with values = CreateUserReq.parse(ctx.request.body)
    console.log(args, args.body.uid, args.body.name);
    ctx.body = { message: "create", id: "123" } as ICreateUserRes;
  }

}

export { UserController };
  1. Init SwaggerRouter Instance
// file -> ./routes/index.ts
import { SwaggerRouter } from 'koa-swagger-decorator'
import { UserController } from './controller/user'

const router = new SwaggerRouter({
  spec: {
    info: {
      title: "Example API Server",
      version: "v1.0",
    },
  },
  swaggerHtmlEndpoint: '/swagger-html',
  swaggerJsonEndpoint: '/swagger-json',
});

// apply swagger docs routes
router.swagger();

// register user defined routes implementation
router
  .applyRoute(UserController)
  // .applyRoute(DemoController); // chained for more then one controller imports

export {router}
  1. Init Koa Application
// file -> ./main.ts
import {router} from './routes/index'
const app = new Koa();
app
  .use(cors())
  .use(bodyParser())
  .use(router.routes())
  .use(router.allowedMethods());

export default app.listen(config.port, () => {
  console.log(`App is listening on ${config.port}.`);
});

// running ts-node ./main.ts and that's all

Define query/path params

@routeConfig({
  method: "get",
  path: "/user/{uid}",
  summary: "get user by id",
  description: "detailed user",
  tags: ["USER"],
  operationId: "GetUserById",
  request: {
    params: z.object({
      uid: z.string().nonempty(), // path params, don't forget to add {uid} in [path] field.
    }),
    query: z.object({
      count: z.coerce.number().default(10), // using z.coerce to convert query string into number & validate
      limit: z.coerce.number().default(0),
    })
  },
})

Define body/responses params

define params by adding @body/@responses decorators to your handler

  @routeConfig({
    path: "/users/update",
    method: "put",
    tags: ["USER"],
    operationId: "UpdateUser",
  })
  @body([ZodSchema])
  @responses([ZodSchema])

Using router.prefix

const router = new SwaggerRouter({});

router.prefix("/api");

by calling router.prefix, your swagger docs routes & biz routes will automatically add prefix "/api". Open http://localhost:3000/api/swagger-html to get swagger docs.

Using Middlewares

using @middlewares decorator for your handler method

  @middlewares([
    async (ctx, next) => {
      const x = ctx._swagger_decorator_meta as ItemMeta; // get swagger decorator meta info through ctx
      console.log("biz mid", x.routeConfig);
      await next();
    },
  ])

TODO List

  • support request validation
  • support response validation
  • support middleware decorator
  • support adding exist components to spec
  • support generate openapi docs without starting app
  • add unit test
  • support form-data request
  • support define non-200 responses
  • support class decorators

koa-swagger-decorator's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

koa-swagger-decorator's Issues

pm2支持

请问如果使用pm2启动koa-swagger-decorator,要如何实现?
目前已试过如下方式,但都不行,包括:
pm2 start my_app.js --node-args="--harmony"
创建一个server.js文件, require('babel-register'); require('babel-polyfill'); require('./app')
pm2 start ecosystem.config.js --interpreter babel-node
以上三种方式都不无法使用pm2运行起来
但这种方式可以:
新建一个server.js
require('child_process').exec('babel-node bin/www');
然后pm2运行server.js,可以运行起来
如果查看pm2 log 其实还是报错import的错误
但实际上站点可以运行起来
回归,请问如果使用pm2启动koa-swagger-decorator,要如何实现?

how to add baseic auth api_auth and user auth in swagger doc

hello sir
basically, I want to add three middleware in my code
1> basic auth with userName and password
2> api auth with api_key
3> user auth with authorization

i have implemented user auth, API auth but Api auth is not working only user auth is able to use it
In the ctx.request.headers i am getting only single key Authorization with token but i want a api-key so that i can implement api auth and user auth middlewear on my code
Please help me as soon as possible thanx in advance
Screenshot from 2019-04-29 19-44-39
Screenshot from 2019-04-29 19-48-46

array request body

Hi,

Is there a way to do a request body type of array?

something like

@body({
  type: 'array',
  properties: {
     key:{
        type:'string'
     },
     value:{
        type:'string'
     }
  }
})

From reading the source code, it seems the body is hard-coded to 'object' but I might have missed something.

Thanks!

how i can send multiple value in header in swagger

hi
i want to pass multiple key in header but i am able to pass single key value
is it possible to pass multiple key in this swagger..? if yes please let me know how will we do it as soon as possible ..

Add path prefix to default json file since swaggerJSON is not guaranteed to be created in root path/给json文件添加默认路径

router.swagger = (options) => { // 设置swagger路由 router.get('/swagger-json', async (ctx) => { ctx.body = buildSwaggerJson(options); }); router.get('/swagger-html', async (ctx) => { ctx.body = swaggerHTML('/swagger-json'); }); };
in above method, swagger json can be generated in paths other than root path, e.g. under /api/swagger-json.
我的代码API分3个文件,分别置于/api,/auth,/upload目录下,只希望暴露/api目录下的文件

duplicate class name can cause an error for swagger json generating process

please give all your router class a uniq name, or it would be difficult for auto generate swagger json process.

solution: name your router class differently.
eg.

// routes/fileA.js
export default class ARouter {
  @request('GET', '/A')
  ...
}

// routes/fileB.js
export default class ARouter {
 // will cause error
 @request('GET', '/B')
 ...
}

// routes/fileB.js
export default class BRouter {
 // will perform well
 @request('GET', '/B')
 ...
}

support { type:'array' } for query params

@query({ parameters: {
type: 'array',
required: false,
items: { type: 'string' },
default: ['parameters1'],
collectionFormat: 'multi',
uniqueItems: false
}})
當 query 參數只有一個項目時
在 validate(ctx.params, parameters.query);
會因為 ctx.params 不為 array 導致錯誤

apiKey 不起作用

根据文档编写 swaggerOptions 如下:
image

但在 swagger-html 中进行 “try it out”,并不会自动把 token 加到 header 中去

请问是否需要进行其它参数的设置?

使用koa 如何在 实现文档

通过 app.use(controller());
function addControllers(router, dir) {
fs.readdirSync(__dirname + '/' + dir).filter((f) => {
return f.endsWith('.js');
}).forEach((f) => {
logger.info(process controller: ${f}...);
let mapping = require(__dirname + '/' + dir + '/' + f);
addMapping(router, mapping);
});
}

module.exports = function (dir) {
let
controllers_dir = dir || 'controllers',
router = require('koa-router')();
addControllers(router, controllers_dir);
return router.routes();
};

接口是这么实现的
const remote=require('../base/middleware/client')
module.exports = {
'POST /client': async (ctx, next) => {
try {
let data=ctx.request.body;
let result=await remote.remote('ETL','words',null)
let str=result.data;
ctx.body= 'get data from'+str;
}catch(e){
logger.error(e.message);
}
next();
}
};

这样来实现接口的 我只想实现接口文档 应该怎么弄?

support body params to be an array.

目前遇到了一個難題
本身的Params就是一個Array的形式
Example:
[ { 'Key': 'Value' } ]
然而 @Body 只支援
{ 'List': [ { 'Key': 'Value' } ] }
是否能加入一個 @Bodys 的方法
讓swagger.json中的
schema: { type: 'object' }
變成
schema: { type: 'array' }

@next milestone

  • remove all babel / js files

  • expose more decorators for convenience

  • seperating router related decorators & pure swagger related decorators

  • 90%+ test coverage

  • using OpenAPI v3

  • generating d.ts definitions for swagger definitions

Ignore .test file when use mapDir

With SwaggerRouter when I use router.mapDir, it will cause the error with my .test.ts file!!

If I add options recursive: false, it will be ok to open swagger, but all api will get 405 error. I can't figure out why...

Currently, I use router.map instead of router.mapDir, works well but I have to add every controller class by myself.

Did there anyone with the same issue or have any idea to use router.mapDir with .test file in the same folder?

issue in compileing the ramda in nodemodule

node_modules/koa-swagger-decorator/dist/decorators.d.ts:1:8 - error TS1192: Module '"/home/appinventiv/Desktoep/Backend/node_modules/@types/ramda/index"' has no default export.

1 import _ from 'ramda';
use in decorators.ts file as
import * as _ from 'ramda'
issue

string checker for null value

For null value, when I defined the schema as "type:string" then the checker would not allow a field with null value as "{ "field_name":null} passed.
So I want to ask a question, so as the design of koa-swagger-decorator, a null value is not a string and if we would pass a 'null value', we have to pass '' at least?

Better validator

Hello,当我使用validate功能时,发现只会说incorrect field,但不会明确的指出应该是什么。我看了源码发现你的validate是自己定义的规则与校验方式,为什么不使用https://github.com/node-modules/parameter 这个更通用一点方式呢?当然,这需要做swagger和paramter两种结构间的协调。

怎么创建一个父类定义通用的接口模板

想定义一个父类定义通用接口模板,但是static方法没有办法被子类继承。
类似于这样

class Common {

		@request('GET', `/${pathname}/{_id}`)
		@summary(`获取${name}详情`)
		@description('_id不能为空')
		@tag
		@path({
			_id: {
				type: 'ObjectId',
				required: true
			}
		})
		@responses(example)
		static async get(ctx) {
			const {_id} = ctx.params
			return model
				.findById({_id})
				.then(row => ctx.body = row)
		}
}

试着在子类对象上手动添加父类方法,但是执行时target 一直指向的是Common类

	Object.getOwnPropertyNames(CommonClass)
		.filter(method => !['name', 'constructor', 'length', 'prototype'].includes(method))
		.forEach(funName => {
			const newName = `${funName}_${name}`
			if(Object.getOwnPropertyNames(ApiClass).indexOf(newName) < 0) {
				let func = ApiClass[newName] = CommonClass[funName]
			}
		})

class annotation @responsesAll has conflict with @responses

koa-swagger-decorator=1.6.8
koa=2.5.1

reproduce:

@tagsAll(["授权"])
@responsesAll({
    400: { description: "参数校验失败" },
})
export default class AuthController {
    @summary("登录授权")
    @description("登录授权")
    @body((LoginParameters as any).swaggerDocument)
    @request("post", "/api/auth/local")
    @responses({
        // ...commonResponses,
        200: { description: "授权成功" },
    })
    public static async login(ctx) {
        const data = ctx.validatedBody;
        ctx.body = await AuthenticateService.authenticate(data);
    }
}

this creates only status code 400 in generated swagger ui but expecting both 400 and 200 responses

workaround:

const commonResponses = {
    400: { description: "参数校验失败" },
};

@tagsAll(["授权"])
export default class AuthController {
    @summary("登录授权")
    @description("登录授权")
    @body((LoginParameters as any).swaggerDocument)
    @request("post", "/api/auth/local")
    @responses({
        ...commonResponses,
        200: { description: "授权成功" },
    })
    public static async login(ctx) {
        const data = ctx.validatedBody;
        ctx.body = await AuthenticateService.authenticate(data);
    }
}

Hope this can be resolved in @next milestone #91

这个组件可以用在typescript的koa项目中吗?

这个组件可以用在typescript的koa项目中吗?我在我们的TS项目中尝试集成这个组件,最终在API列表中显示No operations defined in spec!。我们的TS项目最终编译出来的是一个webpack打包JS文件,会和这个有关系吗?

There are some problems with the use of docker

Do not use docker can be normal operation, use docker on the error.
ide: webstrom
windows10
docker 19
node 10.16

package.json

{
  "name": "keyword_filter",
  "description": "keywork filter",
  "author": "kenes",
  "version": "0.0.1",
  "private": true,
  "license": "UNLICENSED",
  "main": "dist/server.js",
  "scripts": {
    "watch": "nodemon --watch 'src/**/*' -e ts --exec 'ts-node' src",
    "build": "npm run lint && tsc",
    "start": "npm run build && node dist"
  },
  "keywords": [],
  "dependencies": {
    "@koa/cors": "^3.0.0",
    "class-validator": "^0.9.1",
    "connection-string-parser": "^1.0.3",
    "dotenv": "^8.2.0",
    "koa": "^2.11.0",
    "koa-bodyparser": "^4.2.1",
    "koa-helmet": "^4.2.0",
    "koa-jwt": "^3.6.0",
    "koa-logger": "^3.2.1",
    "koa-redis": "^4.0.1",
    "koa-router": "^7.4.0",
    "koa-swagger-decorator": "^1.6.0",
    "reflect-metadata": "^0.1.13",
    "sqlite3": "^4.1.1",
    "typeorm": "^0.2.22",
    "winston": "^3.2.1"
  },
  "devDependencies": {
    "@types/dotenv": "^6.1.1",
    "@types/koa": "^2.11.0",
    "@types/koa-logger": "^3.1.1",
    "@types/koa-redis": "^3.0.3",
    "@types/koa-router": "^7.4.0",
    "@types/mysql": "^2.15.8",
    "@types/node": "^12.12.26",
    "@types/webpack": "^4.41.5",
    "cross-env": "^5.2.0",
    "nodemon": "^1.19.4",
    "ts-loader": "^6.2.1",
    "ts-node": "^8.6.2",
    "tslint": "^5.20.1",
    "typescript": "^3.7.5"
  },
  "engines": {
    "node": ">= 7.6"
  }
}

Dokerfile

FROM node:lts-alpine as builder

WORKDIR /server

ADD . /server

RUN npm install --registry=https://registry.npm.taobao.org
RUN npm run build

FROM node:lts-alpine as dist

WORKDIR /server

COPY --from=builder /server/dist .
COPY --from=builder /server/node_modules node_modules

CMD [ "node", "." ]

the error:
/server/node_modules/any-promise/register-shim.js:2 module.exports = require('./loader')(window, loadImplementation) ^ ReferenceError: window is not defined at Object.<anonymous> (/server/node_modules/any-promise/register-shim.js:2:38) at Module._compile (internal/modules/cjs/loader.js:955:30) at Object.Module._extensions..js (internal/modules/cjs/loader.js:991:10) at Module.load (internal/modules/cjs/loader.js:811:32) at Function.Module._load (internal/modules/cjs/loader.js:723:14) at Module.require (internal/modules/cjs/loader.js:848:19) at require (internal/modules/cjs/helpers.js:74:18) at loadModule (/server/node_modules/koa-swagger-decorator/dist/utils.js:45:17) at loadClass (/server/node_modules/koa-swagger-decorator/dist/utils.js:54:17) at /server/node_modules/koa-swagger-decorator/dist/utils.js:64:26
Thanks!

root cause: compatible with router.use()

根本问题在于:我代码的结构
app.js:
router.use('/auth', auth.routes()); router.use('/api', api.routes())
api.js:

wrapper(router);
router.swagger(...)
// map all static methods at classes for models
router.map(Product);

期待的表现:

  • API的访问解析正确,例如:访问/api/product 可以正确返回product列表 --完美
  • swagger-html的访问解析正确,例如: /api/swagger-html能显示文档页面 --几乎完美,可以显示
  • 默认的swagger-json的地址访问正确,例如:默认取得的是/api/swagger-json文件 -- 用昨天的修改能workaround,但是能默认加上对应的路径/api就完美了。
  • 默认的swagger文档的地址正确,文档页面中product文档的路径应该是/api/product --现在是/product,这个希望能修改,到底map函数是神来之笔,非常棒。如果能和router.use()兼容就完美了。

加油⛽️,我有空了争取钻一下router.use代码,找找解决方法

可以为responses装饰器的参数添加一个类型的描述吗?

因为我想在ts的项目中使用这个库,
但是用到responses装饰器的时候,会通过参数的默认值,将responses装饰器的参数推断为{ 200: { description: string }},因而在我想描述500这个状态码的时候,编辑器中会出现错误提示

validate时,顶层属性存在undefined default value的副作用

例如

@body({
  key1: { type: 'string' },
  key2: { type: 'number' },
})
async test(ctx) {
  console.log(ctx.validatedBody);
}

当遇到传入body为

{
  key1: 'abc'
}

console.log(ctx.validatedBody); 会输出为

{
  key1: 'abc',
  key2: undefined
}

也就是Object.keys(ctx.validatedBody)不会deep equal Object.keys(ctx.request.body),validate代码存在assign undefined default value的副作用

securityAll does not work while security works

swaggerOptions: {
    securityDefinitions: {
        Bearer: {
            type: 'apiKey',
            in: 'header',
            name: 'Authorization'
        }
    }
}
@securityAll([{ Bearer: [] }])
class Test {
    @request('get', '/hello')
    public static helloWorld(ctx): void {
    }
}

The securityAll class annotation does not work.
I expected the securityAll annotation would make all requests in the class get the same result of the security annotation per request.
On the swagger page, the page should add an Authorization header into the requests but the securityAll annotation does not affect the requests in the class on the swagger page.
image

class Test {
    @request('get', '/hello')
    @security([{ Bearer: [] }])
    public static helloWorld(ctx): void {
    }
}

The security annotation added per request works fine.
The security annotation makes the swagger page add a corresponding header into the request being fired.
image

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.