elysiajs / elysia-swagger Goto Github PK
View Code? Open in Web Editor NEWA plugin for Elysia to auto-generate Swagger page
License: MIT License
A plugin for Elysia to auto-generate Swagger page
License: MIT License
Ola,
I want to make a nice swagger schema view of my api. While the @elysia/swagger plugin gets there half of the way, it seems to struggle with schemas. I've dug through all the examples, but none seem to mix params and responses together, or the examples are outdated. (Like the readme of this repo). I've tried different ways of creating layouts for the schemas, but non of them seem to work properly.
I've got an Elysia route set up like the following:
import { Elysia, t } from "elysia"
import { song } from "../../interfaces";
import { db } from "../..";
export const getSkip = (app: Elysia) => app
.get("/skip/:id", ({ params }) => {
return db.query("SELECT user_id, start, end FROM skip WHERE `song_id`=$songId").all({
$songId: params.id
}) as song[];
}, {
params: t.Object({
id: t.Number({
description: "The ID of the song you want to fetch skips from."
})
}, {
description: "Request body to get all skips belonging to a song."
}),
response: {
200: t.Array(
t.Object({
user_id: t.Number({
description: "The Soundcloud id of the uploader.",
minimum: 0
}),
start: t.Number({
description: "Timestamp where the song skip starts.",
minimum: 0
}),
end: t.Number({
description: "Timestamp where the song skip ends.",
minimum: 0
})
}), {
description: "Returns an array of skip items belonging to the song id."
}
),
},
type: ["arrayBuffer", "application/json"],
description: "Gets all skips of a song.",
})
Then I run the route like this:
new Elysia()
.use(swagger(swaggerDocs)) //Basic dcoumentation options
.group("/skip", app => app
.use(getSkip) //The route from above
)
.listen(port, () => console.log(`Listening on port ${port}...`));
My swagger starts up successfully, but some functionality is broken. I can not inspect the schema, and can not log into my oauth.
I threw the generated swagger schema into insomnia to inspect any errors, it came out with the following:
{
"openapi": "3.0.3",
"components": {
"schemas": {}
},
"security": [
{
"ApiKeyAuth": [] ❌ <--- Api "security" values must match a scheme defined in the "components.securitySchemes" object
}
],
"servers": [
{
"description": "Local host",
"url": "http://localhost:3001"
}
],
"info": {
"title": "Skipcloud API",
"description": "API used to upload sections of soundcloud songs to skip over.",
"version": "0.0.1",
"contact": {
"name": "@hang_yourself on discord"
}
},
"paths": {
"/skip/{id}": {
"get": {
"parameters": [
{
❌ <--- "0" property must have required property "schema".
"description": "The ID of the song you want to fetch skips from.",
"type": "number",
"in": "path",
"name": "id",
"required": true
}
],
"responses": {
"200": {
"description": "Returns an array of skip items belonging to the song id.",
"items": { ❌ <--- Property "items" is not expected to be here
"type": "object",
"properties": {
"user_id": {
"description": "The Soundcloud id of the uploader.",
"minimum": 0,
"type": "number"
},
"start": {
"description": "Timestamp where the song skip starts.",
"minimum": 0,
"type": "number"
},
"end": {
"description": "Timestamp where the song skip ends.",
"minimum": 0,
"type": "number"
}
},
"required": [
"user_id",
"start",
"end"
]
},
"content": {
"arrayBuffer": {
"schema": {
"type": "array"
}
},
"application/json": {
"schema": {
"type": "array"
}
}
}
}
},
"operationId": "getSkipById"
}
}
}
}
I have an API key in my header, I have defined my swagger schema as such:
import { ElysiaSwaggerConfig } from "@elysiajs/swagger/src/types";
export default {
documentation: {
components: {
securitySchemes: {
ApiKeyAuth: {
type: "apiKey",
in: "header",
name: "Authorization",
description: "Key used to log into the soundcloud API."
}
},
//... etc
}
} as ElysiaSwaggerConfig;
This however does not show up in the schema. The schema stays empty.
The first error seems to be caused by not putting "type: number" in a schema object:
"parameters": [
{
"description": "The ID of the song you want to fetch skips from.",
"schema": {
"type": "number"
},
"in": "path",
"name": "id",
"required": true
}
],
This error seems to be caused by the response schema directly being in the 200 object, instead of inside the content objects.
"responses": {
"200": {
"description": "Returns an array of skip items belonging to the song id.",
"content": {
"arrayBuffer": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"user_id": {
"description": "The Soundcloud id of the uploader.",
"minimum": 0,
"type": "number"
},
"start": {
"description": "Timestamp where the song skip starts.",
"minimum": 0,
"type": "number"
},
"end": {
"description": "Timestamp where the song skip ends.",
"minimum": 0,
"type": "number"
}
},
"required": [
"user_id",
"start",
"end"
]
}
}
},
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"user_id": {
"description": "The Soundcloud id of the uploader.",
"minimum": 0,
"type": "number"
},
"start": {
"description": "Timestamp where the song skip starts.",
"minimum": 0,
"type": "number"
},
"end": {
"description": "Timestamp where the song skip ends.",
"minimum": 0,
"type": "number"
}
}
}
}
}
}
}
}
With these manual fixes, I end up with the schema view that I want:
I hope this post clearly outlines the issues I am facing with the swagger plugin. If there is any context missing, please let me know.
Please don't be another stupid oversight by myself🙏
Running into Schemas with 'type: array', require a sibling 'items: ' field
error when validating the OpenAPI schema that's generated by the plugin.
// this is my endpoint
.get("/", ({ store: { db }, orgId }) => getCustomers(db(), orgId), {
response: t.Array(Customer),
})
// this is the type
import { BaseDbModel } from "./baseTypes";
import { Static } from "@sinclair/typebox";
import { t } from "elysia";
export const CustomerParams = t.Object({
number: t.Optional(t.Union([t.String(), t.Null()])),
});
export type CustomerParams = Static<typeof CustomerParams>;
export const Customer = t.Intersect([
BaseDbModel,
CustomerParams,
t.Object({ orgId: t.String() }),
]);
export type Customer = Static<typeof Customer>;
Would appreciate any ideas!
The scalarConfig
object is ignored for the most part, instead of actually applying to the Scalar UI. As a result, almost all these options have no effect if you try to set them:
I checked the source code and found that only the customCss
and spec.url
options are applied.
Is this an oversight, or deliberate?
If I understand the docs correctly, it could be fixed with this:
<script
id="api-reference"
data-url="${config.spec?.url}"
data-configuration="${JSON.stringify(config)}"
></script>
Using:
Based on
The schema keywords should go under schema
key.
Example code:
import swagger from '@elysiajs/swagger';
import { Elysia, t } from 'elysia';
const app = new Elysia()
.use(swagger())
.get('/', async () => 'hi', {
query: t.Object({
a: t.Optional(t.String({
title: 'title',
enum: ['a', 'b'],
pattern: 'a|b',
default: 'b',
minLength: 1,
maxLength: 10,
})),
}),
})
.listen(3000);
console.log(
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`,
);
Current parameter json
{
"parameters": [
{
"title": "title",
"enum": [
"a",
"b"
],
"pattern": "a|b",
"default": "b",
"minLength": 1,
"maxLength": 10,
"schema": {
"type": "string"
},
"in": "query",
"name": "a",
"required": false
}
]
}
Expected parameter json
{
"parameters": [
{
"schema": {
"title": "title",
"enum": [
"a",
"b"
],
"pattern": "a|b",
"default": "b",
"minLength": 1,
"maxLength": 10,
"type": "string"
},
"in": "query",
"name": "a",
"required": false
}
]
}
Is somehow possible to include descriptions for the paths in the generated docs, e.g. from docblock/comments?
I have model defined like
t.Object({
numericProp: t.Nullable(t.Number()),
})
This generates expected type TUnion<[TNumber, TNull]>
.
And in OpenAPI I'm getting
"numericProp": {
"anyOf": [
{
"type": "null"
},
{
"type": "number"
}
]
}
OpenAPI spec says
OpenAPI 3.0 does not have an explicit null type as in JSON Schema, but you can use nullable: true to specify that the value may be null. Note that null is different from an empty string "".
So, the expected OpenAPI schema would be
"numericProp": {
"type": "null",
"nullable": true
}
SwaggerUI also has problem rendering model example, saying "numericProp": "Unknown Type: null",
Maybe we could include path
by default in the exclude
array?
Line 111 in 7a42bf0
Example code:
import { Elysia } from "elysia";
import { swagger } from '@elysiajs/swagger'
new Elysia()
.use(swagger({path: "/docs"}))
.listen(3000, ({ hostname, port }) => {
console.log(`Running at http://${hostname}:${port}`)
})
In my app, I have the following Elysia/Typebox schema:
const userSchema = t.Object({
...,
password: t.Never(),
})
This prevents the API from sending the password field as part of its response.
But even with that, the password field is shown in the Swagger generated docs. I strongly believe it shouldn't be there.
After updating to Elysia 0.7 and all dependencies swagger is not filling default values when provided in schema.
Default value is present in the /json
endpoint, seems like an issue in the frontend?
Swagger is version 2.0 of OpenAPI, we should promote the OpenAPI standard :) especially since OpenAPI 4.0 is being released this year!
Wanted to open a discussion about renaming or potentially having a new package release with the OpenAPI name?
Thoughts? 🤔 @SaltyAom
I would like to compose routes and not use multidimensional nesting
import http from 'node:http';
import { createServerAdapter } from '@whatwg-node/server';
import { Elysia, t } from 'elysia';
import { swagger } from '@elysiajs/swagger';
const setup = (app: Elysia) =>
app.use(
swagger({
path: '/docs',
provider: 'swagger-ui',
})
);
// Dont work
const router = new Elysia({
prefix: '/:accountId',
name: 'account-router',
}).guard({
headers: t.Object({
'x-remote-user': t.String({ title: 'Remote User', description: '...' }),
}),
});
// Dont provide headers schema
router.get('/', ({ headers }) => {
return {
from: headers['x-remote-user'],
};
});
// Work
const router2 = new Elysia({
prefix: '/v2/:accountId',
name: 'account-router',
}).guard(
{
headers: t.Object({
'x-remote-user': t.String({
title: 'Remote User',
description: '...',
}),
}),
},
// Its ok provided
(app) =>
app.get('/', ({ headers }) => {
return {
from: headers['x-remote-user'],
};
})
);
const app = new Elysia().use(setup).use(router).use(router2);
const adapter = createServerAdapter(async (request) => {
return app.handle(request);
});
http.createServer(adapter).listen(3000);
> router/account.ts
export const account = new Elysia()
.group('/account', (app) =>
app
.decorate('Login', LoginController)
.use(jwtAccessSetup)
.use(jwtRefreshSetup)
.post('/sendcode', async ({ body, Login }) => {
return await Login.sendcode(body)
},
{
body: t.Object({
phone: t.String({ pattern: '^[0-9]{10,16}$', default: '79251234567' }),
}),
detail: {
tags: ['accounts']
},
response: {
200: t.Object({
data: t.Object({
code: t.Number({ enum: [1234] }), // ONLY FOR DEV MODE. SHOULD BE DELETED IN PROD
message: t.String({ enum: ['success'] }),
})
}),
500: t.Object({
message: t.String({ enum: ['Internal Server Error'] }) // Specify the exact error message
})
},
})
> index.ts
const app = new Elysia()
app
.use(swagger()
.use(account)
.listen(3000);
If you specify response as here click
u will get an error while trying to make responce like this one
cause when some values in response apeared then elysia or ts (i dont know exactly) try to look not on body validation but on response validation while making request to server.
Expect behavior: when the client makes a request to the server, the validation of the body should be based on the schema that lies in the body, not in the response.
When using Elysia like this:
import { Elysia } from "elysia";
import { swagger } from "@elysiajs/swagger";
const app = new Elysia({ prefix: "/api" }).use(
swagger({
path: "/swagger",
})
);
app.get("/", (ctx) => ctx.headers);
app.listen({ port: 3003, hostname: "0.0.0.0" });
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
One would expect that swagger would be exposed at /api/swagger
. Which it is, but opening the page you get the following error:
The JSON is resolvable at /api/swagger/json
, under the /api
prefix as expected, but the swagger try to fetch it from /swagger/json
.
The changes introduced in #59 breaks in the case where swagger
is mounted with path
in the subdirectory.
const app = new Elysia()
.use(
swagger({
path: "/services/docs",
})
)
.get("/services/hello", () => ({ ok: true }));
When going to /services/docs
, I get a blank page. This is because Scalar tries to load the data from /services/services/docs/json
.
Current workaround is to manually override the scalarConfig
const app = new Elysia()
.use(
swagger({
path: "/services/docs",
scalarConfig: {
spec: { url: "/services/docs/json" },
},
})
)
.get("/services/hello", () => ({ ok: true }));
Hi team. Can we please enforce a ordering of the tags and endpoints?
I would be very happy if we could sort by: tag name, then, the endpoint URL (ascending)
Every time I made a change to endpoints, my Swagger documentation orders differently.
How to reproduce:
When using reference models with swagger we hit a NOT_FOUND error (elysia side)
and Resolver error at paths./experiences.post.requestBody.content.application/json.schema.$ref Could not resolve reference: Could not resolve pointer: /components/schemas/experienceRequest does not exist in document
(Swagger side)
models/elysia/experienceRequest.models.ts
import Elysia, { t } from "elysia";
export const experienceRequest = new Elysia()
.model({
'experienceRequest': t.Object({
type: t.String(),
title: t.String(),
summary: t.String(),
})
})
plugins/experiences.ts
import { Domain, Experience } from 'models/database';
import { experienceRequest } from 'models/elysia'
import { Elysia } from 'elysia';
import { corsConf } from './corsConf';
import { userLogged } from './userLogged';
import { CannotSaveExperienceError, DomainDoesNotExistError } from 'errors';
export const experiences = new Elysia()
.use(corsConf())
.use(experienceRequest)
.get('/experiences', async () => await Experience.find())
.use(userLogged)
.post(
'/experiences',
async ({ body, userId }) => {
const domain = await Domain.findOne({ admin: userId });
if (!domain) throw new DomainDoesNotExistError(userId)
let experience = new Experience()
experience.title = body.title
experience.summary = body.summary
experience.type = body.type
const result = await experience.save();
if (!result) throw new CannotSaveExperienceError(userId)
domain.experiences.push(result.id)
await domain.save()
return result
},
{
body: 'experienceRequest',
detail: {
summary: 'Add new experience'
}
}
)
index.ts
import mongoose from 'mongoose';
import { Elysia } from 'elysia'
import { experiences, googleAuth, domain } from 'plugins';
import { validateEnvironment } from './services/validation';
import swagger from '@elysiajs/swagger';
import { errors } from 'errors';
validateEnvironment();
await mongoose.connect(Bun.env.MONGO_URL ?? '');
const app = new Elysia()
.use(swagger())
.error(errors)
.onError(({ code, error }) => {
console.error(error)
return error.message;
})
.use(domain)
.use(googleAuth)
.use(experiences)
.listen({
hostname: Bun.env.HOSTNAME || "::",
port: Bun.env.PORT || 3000,
tls: Bun.env.TLS_PASSPHRASE ? {
cert: Bun.file('./cert.pem'),
key: Bun.file('./key.pem'),
passphrase: Bun.env.TLS_PASSPHRASE
} : undefined
})
console.log(`Running at http://${app.server!.hostname}:${app.server!.port} CORS allowed: ${JSON.stringify(Bun.env.ALLOWED_DOMAINS)}`)
export type Portfolio = typeof app;
Line 23 in c92aa0f
Line 112 in c92aa0f
Because this library is downloading swagger-ui-bundle.js and swagger-ui.css it means it cannot be used without internet access, or behind a many firewalled enterprise networks, unfortunately.
It would be nice if you could remove this internet dependency and package the required swagger files locally within this library :)
When trying to access the swagger url, it greets me with the following error:
And then when going to the direct JSON endpoint it shows me this in the browser:
Object.entries requires that input parameter not be null or undefined
Though, this only happens when being on the experimental
version from elysia
. I need that version though, because of the other routing bug in latest
👀
I don't know if I should report it here or in the other repository.
Example Code:
import { Elysia } from "elysia";
import { swagger } from "@elysiajs/swagger";
export const app = new Elysia()
.use(swagger({
path: "/api/swagger",
swagger: {
info: {
title: "e.bio API",
version: "2.0.0-beta"
}
}
}));
app.get("/api", () => {
res.send("Hello World!");
});
Also, I don't know why, but the specified title
and version
aren't showing either within Swagger on elysia@latest
since a few weeks.
Swagger doesn't appear to generate documentation if the path is set to '/', an example here:
import { Elysia } from 'elysia';
import { swagger } from '@elysiajs/swagger'
const app = new Elysia()
.use(swagger({
path: '/',
documentation : {
tags : [
{ name: 'tag1', description: 'desc1'},
]
}
}))
.get('/test', () => "Test", {
detail : {
tags: ['tag1']
}
})
.listen(3000)
The above only generates the background page for the swagger documentation, while if you adjust the path parameter to anything else or even remove it, it appears to work.
I can write a workaround to achieve the intended effect, but it does appear to be a bug.
Updated to version "@elysiajs/swagger": "^0.7.2",
and am now getting the following error when I navigate to the swagger url:
🦊 Elysia is running at localhost:port
56 | registerSchemaPath({
57 | schema,
58 | hook: route.hooks,
59 | method: route.method,
60 | path: route.path,
61 | models: app.definitions.type,
^
TypeError: undefined is not an object (evaluating 'app.definitions.type')
at /node_modules/@elysiajs/swagger/dist/index.js:61:28
at forEach (:1:20)
at /node_modules/@elysiajs/swagger/dist/index.js:55:12
GET - /swagger/json failed
Reverting to version 0.6.2
resolves the error
I have the following swagger options object:
const swaggerConfig = {
path: "api-docs",
documentation: {
info: {
title: "MyAPI",
version: "1.0.0",
description: "API Description",
contact: {
name: "My Name",
email: "[email protected]",
},
},
},
};
And the fields:
documentation -> info -> contact -> url
documentation -> externalDocs -> url
Show the url: localhost:8000/{url}
instead of: url
at the Swagger web interface.
I mean the URLs are relative to the actual URL and not absolute as they should be.
The URLs should be edited removing the actual URL where the Swagger web interface is at.
Instead of ...documentation.info as is I think the URL element has to be changed before, for example wrapping the actual object with a function that receive the object and the path
(you already have it at this context) and then replaces all the path occurrences by an empty string. It's an idea feel free to change the approach ;-)
Thanks for the amazing framework and each of the plugins, they are awesome <3
I would love to use this on cloudflare but when I configure elysia with aot: false
it will not generate the endpoints in the swagger definition.
Is this a fundamental caveat of using dynamic mode because types and endpoints can not be inferred without this step, or is there a way to make it work with dynamic mode?
See also discussion here: elysiajs/elysia#58
I am attempting to use a non default content type "text/event-stream" and have a very hard time figuring out how to specify this.
The content type is taken from app.routes -> hooks.type
, but how do you actually set this value? Can an example please be added to the documentation?
With Elysia 0.2.6 this module fails looking for DEFS
Import named 'DEFS' not found in module
in index.ts line 1
import { type Elysia, SCHEMA, DEFS } from 'elysia'
Hi! I have the following Elysia request:
.get('/:id', (c) => getEventDeploy(c.params.id, c.headers), {
params: getDeployParamsSchema,
detail: {
summary: 'Returns the progress entries for the event with the given id.'
},
type: 'application/json'
})
The Swagger generator generates an invalid entry for this request by default and the validation errors are as follows:
"get": {
"parameters": [
{
"pattern": "^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$",
"default": "",
"description": "The event id to get logs for",
"type": "string",
"in": "path",
"name": "id",
"required": true
}
],
"operationId": "getById",
"summary": "Returns the progress entries for the event with the given id.",
"responses": { "200": {} }
}
In order to get a properly validated parameters object, I have to specify the following manually in parameters:
.get('/:id', (c) => getEventDeploy(c.params.id, c.headers), {
params: getDeployParamsSchema,
detail: {
summary: 'Returns the progress entries for the event with the given id.',
parameters: [
{
schema: getDeployParamsSchema.properties.id,
name: 'id',
in: 'path',
required: true
}
]
},
type: 'application/json'
})
This generates the following schema, which does not error out at param validation:
Should this (generating the schema
object for each param) not be the default behavior? Thanks!
When ever I use elysia swagger with elysiajs/cors plugin swagger page doesn't loads and shows a blank page.
When i remove this line app.use(cors());
it starts to work.
version of each pakage are here.
"elysia": "^1.0.9",
"@elysiajs/cors": "^1.0.2"
"@elysiajs/swagger": "^1.0.3"
Bun v1.0.15
Elysia: v0.8.8
Elysia/swagger: v0.8.0
When adding a prefix to my Elysia configuration:
export const elysia = new Elysia({ prefix: BASE_PATH })
I believe I would need to be able to then apply a basePath
configuration per the Swagger object config: https://swagger.io/specification/v2/
// pseudo code that does not appear to function as expected
.use(swagger({
documentation: {
basePath: BASE_PATH,
info: {}
}
}))
The basePath
does not appear to be a property of the defined type for the documentation
configuration, but this is where it seems to logically appear in the swagger configuration.
When registering a route with the all
method the generated swagger blindly copies the httpMethod which is not valid according to openapi 3.0.3 spec.
app
.use(swagger())
.all("test",() => {});
"/test": {
"all": {
"operationId": "allTest",
"responses": {
"200": {}
}
}
},
The only valid methods are get, put, post, delete, options, head, path, trace
https://swagger.io/specification/
Here we probably should filter by valid methods and in case of all call the register function multiple times.
Lines 132 to 143 in 8eb03f0
I might be doing something wrong, but when i make an endpoint with an enum in the body i don't get the enum in the swagger file
import { Elysia, t } from 'elysia'
export const plugin = new Elysia({
prefix: '/a'
})
.post('/hello-world', ({ body }) => body, {
body: t.Object({
type: t.Enum({
MOD: 'mod_skin',
VIP: 'vip_skin',
})
}),
})
the output is
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"type": {
"anyOf": [
{
"const": "mod_skin",
"type": "string"
},
{
"const": "vip_skin",
"type": "string"
},
vs
schema:
type: string
enum: [asc, desc]
so the rendered output is either
let me know if it's something I can change or if it's something we need to add to the swagger generation code ✨
I would like to have persistAuthorization set to true, near the "window.ui = SwaggerUIBundle({" code in src/index.ts
I guess it would be just great to have an argument called "swaggerParameters", a Record<string, string>, that would be put after "dom_id: '#swagger-ui',"
app.ws()
doesnt seem to have the detail
property and all the websocket routes are generated as if they are a GET
route
When using global cookie signature, swagger docs works on first visit but returns empty page on following refreshes
import swagger from "@elysiajs/swagger";
import { Elysia, t } from "elysia";
const app = new Elysia({
cookie: {
secrets: "test",
sign: ["test"],
},
})
.use(swagger())
.get("/", () => "hi", {
response: t.String({ description: "sample description" }),
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
How simple grouping end point with tags? for example this is my route code
import { Elysia } from "elysia";
import { Post } from '../controllers/post'
export const post = new Elysia({ prefix: '/post' })
.get("/", () => Post.index(), { detail: { tags: ['Post'] } })
.get("/:id", ({ params }) => Post.show(params.id), { detail: { tags: ['Post'] } })
.post("/", () => Post.create(), { detail: { tags: ['Post'] } })
.put("/:id", ({ params }) => Post.update(params.id), { detail: { tags: ['Post'] } })
.delete("/:id", ({ params }) => Post.delete(params.id), { detail: { tags: ['Post'] } })
I must do repeat { detail: { tags: ['Post'] }
in every route for grouping it.
is there simple way to do that?
and then, could we add this params in file as comment, for example
// tags: Post
class Post {
// description: 'get all posts'
static async index () {
return [...]
}
}
If we set the response type as an object containing recursion, an error occurs when opening the endpoint in swagger (and incorrect types are displayed in the example), while scalar stops working altogether.
Full code example:
import { Elysia, t } from "elysia";
import { swagger } from "@elysiajs/swagger";
const app = new Elysia()
.use(
swagger({
provider: "swagger-ui", // scalar doesn't work bruh
path: "/docs",
documentation: {
info: {
title: "test",
version: "1.0.0",
},
tags: [
{ name: "App", description: "General endpoints" },
{ name: "Users", description: "User endpoints" },
],
},
})
)
.get(
"/test",
() => {
return {
test: "321",
part: {
test: "32133",
},
};
},
{
response: {
200: t.Recursive((This) =>
t.Object({
test: t.String(),
part: t.Optional(This),
})
),
},
}
)
.listen({
port: 3000,
hostname: "0.0.0.0",
});
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
is is possible to hide specific endpoint from swagger doc ?
Hello there,
I hope you are well.
I have a RESTful API written in Bun using Elysia. And I integrated @elysia-swagger. In one of my endpoints where I upload multiple files of the same fieldname and validate the body like below.
app.post("/", ({body}) => files, { body: { files: t.Files() } })
When I select application/form-data, while trying to upload files from Swagger UI, the exception is thrown. The error message is 😱 Could not render Parameters, see the console.
Error in console is
SyntaxError: Unexpected token 'F', "Files" is not valid JSON
Thanks!
Is it easy to support OpenAPI 3.1? I want to add null to my types without the Swagger parsers complaining.
Hi there, I was experimenting with elysia for the first time with this simple example:
import { Elysia, t } from "elysia";
import { cors } from '@elysiajs/cors'
import { swagger } from '@elysiajs/swagger'
const app = new Elysia().use(cors()).use(swagger()).get('/id/:id', ({ params: { id } }) => id, {
params: t.Object({
id: t.Numeric()
})
}).get("/", () => "Hello Elysia").listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
I visited http://localhost:3000/swagger to see what the interface looks like and I noticed a request to google is being sent to fetch some fonts.
Somewhat annoyed to see this (after all, it's just a local test of the software and I find myself tracked indiscriminately), I went to search in the repo for references regarding the request without finding anything.
So is it a Swagger issue and should I report it to them or is it due to the implementation done in Elysia?
I had a functional work-around for version 0.8.0, but 0.8.1 replaced the SwaggerUI with Scalar, which does not accept this work-around. I have tried many things, but been unable to find a working solution for referencing schema components that exist within the same OpenAPI file.
I specify the schemas with new Elysia().model({ User: UserModel })
This puts the UserModel into #/components/schemas/User
in the OpenAPI file.
I would like to reference this model (or several) in a response or body, by wrapping it in an object, like so:
new Elysia()
.model({ User: UserModel })
.get("/users", () => {
return { users: [] }
}, {
response: t.Object({
users: t.Array(
// Some reference to '#/components/schemas/User'
)
})
})
Currently, the only way I've found to somewhat get this effect is to put the UserModel
into the array, but this breaks the link to the existing schema. The end result is that the OpenAPI file duplicates the schema, inflating it exponentionally depending on how many times you wish to use it.
{
"openapi": "3.1.0",
"servers": [
{
"url": "http://localhost:3000",
"description": "Test server"
}
],
"components": {
"schemas": {
"User": {
"description": "User",
"additionalProperties": false,
"type": "object",
"properties": {
"email": {
"format": "email",
"type": "string"
},
"id": {
"readOnly": true,
"type": "integer"
},
"name": {
"type": "string"
}
},
"required": [
"email",
"id",
"name"
]
}
}
},
"info": {
"title": "Custom API",
"description": "These are the custom docs",
"version": "1.0.0"
},
"paths": {
"/users/": {
"get": {
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"$ref": "#/components/schemas/User"
}
}
},
"required": [
"users"
]
}
}
}
}
},
"operationId": "getUsers",
"summary": "Get all users"
}
}
}
}
{
"openapi": "3.1.0",
"servers": [
{
"url": "http://localhost:3000",
"description": "Test server"
}
],
"components": {
"schemas": {
"User": {
"description": "User",
"additionalProperties": false,
"type": "object",
"properties": {
"email": {
"format": "email",
"type": "string"
},
"id": {
"readOnly": true,
"type": "integer"
},
"name": {
"type": "string"
}
},
"required": [
"email",
"id",
"name"
]
}
}
},
"info": {
"title": "Custom API",
"description": "These are the custom docs",
"version": "1.0.0"
},
"paths": {
"/users/": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"users": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"email": {
"format": "email",
"type": "string"
}
},
"required": [
"id",
"name",
"email"
]
}
}
},
"required": [
"users"
]
}
}
}
}
},
"operationId": "getUsers"
}
}
}
}
With SwaggerUI, I was able to get this to work with t.Array(t.Ref(UserModel))
, however Scalar does not like this approach, instead attempting to fetch GET /User
as it seems to think the schema is external to the file. I'm not sure I can fault Scalar for this though, as it seems the format was incorrect originally despite working in SwaggerUI.
To get it to work with SwaggerUI, the UserModel was required to specify the $id
field. It seems by setting it to "User", the file ended up with "$ref": "User"
, and the local schema included the "$id": "User"
field. I think the issue is that the $ref
should be #/components/schemas/User
, not just User
. There may be other issues aside from this, but this is the most prominent issue I can spot. I've been reading the OpenAPI v3.1 spec, and I don't think it allows referring to local schemas simply by name, despite them having an $id
, but I could be wrong. Either way, Scalar breaks with how it currently is.
Using:
const UserModel = t.Object(
{
id: t.Integer({ readOnly: true }),
name: t.String(),
email: t.String({ format: "email" }),
},
{
$id: "User",
}
);
const app = new Elysia()
.use(swagger({
documentation: {
openapi: "3.1.0"
}
}))
.model({ User: UserModel })
.get("/users/", () => ({ users: [] }), {
response: t.Object({
users: t.Array(t.Ref(UserModel)),
}),
type: "application/json"
});
Go to /swagger/json
to get the resulting file (Scalar at /swagger
just generates a blank page)
The file looks like this:
{
"openapi": "3.1.0",
"info": {
"title": "Elysia Documentation",
"description": "Development documentation",
"version": "0.0.0"
},
"paths": {
"/users/": {
"get": {
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"users": { "type": "array", "items": { "$ref": "User" } }
},
"required": ["users"]
}
}
}
}
},
"operationId": "getUsers"
}
}
},
"components": {
"schemas": {
"User": {
"$id": "User",
"type": "object",
"properties": {
"id": { "readOnly": true, "type": "integer" },
"name": { "type": "string" },
"email": { "format": "email", "type": "string" }
},
"required": ["id", "name", "email"]
}
}
}
}
If you put it into https://editor-next.swagger.io/ it renders more or less correctly
If you put it into https://docs.scalar.com/swagger-editor you can see that it fails to locate the schema, falling back to null.
Modify the $ref
to be #/components/schemas/User
and it will work.
I added a response with a status code of 400 to the guard hook and a response of 200 to the route. I want to automatically merge rather than overwrite guard hooks in swagger
import { Elysia, t } from "elysia";
import jwt from "../middleware/1.jwt";
import { find } from "lodash-es";
const users = [
{
name: "xxx",
passwd: "123",
},
];
export default new Elysia({ prefix: "/user" }).use(jwt).guard(
{
detail: {
tags: ["User"],
},
error: ({ error }) => {
return {
message: error.message,
};
},
response: {
"400": t.Object({
message: t.String(),
}),
},
},
(app) =>
app.post(
"/login",
async ({ jwt, body: { name, passwd } }) => {
const user = find(users, { name, passwd });
if (!user) {
throw new Error("mm");
}
return {
token: await jwt.sign({
name: user.name,
}),
};
},
{
body: t.Object({
name: t.String(),
passwd: t.String(),
}),
response: {
200: t.Object({ token: t.String() }),
},
}
)
);
Hello! THANKS for making this, I love Elysia API, best I've seen for a web server so far!
3 generators tested, and none are working becauseoperationId
😔 is mandatory for them.
Here,
Line 25 in 959f232
adding a:
method.operationId = "…"
With two possible strategies I can think of:
DELETE /document/{id}
> deleteDocumentById
cool for simple cases, but complicated with /document/{id}/stuff/foo/{bar}
> deleteDocumentByIdStuffFooByBar
lol
.decorateApi("operationId", "foobar")
.decorateApi("description", "my cool op.")
Wha d'ya think?
Cheers.
ERROR :
Refused to load the script 'https://unpkg.com/[email protected]/swagger-ui-bundle.js' because it violates the following Content Security Policy directive: "script-src 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.