Template app based on moleculer, with API Gateway powered by ultimate-express, validation via fastest-validator-decorators and typegoose ORM.
OpenAPI generation provided by @spailybot/moleculer-auto-openapi with Scalar UI and custom OpenAPI decorators.
Bundling powered by rolldown with some fixes.
This template follows the approach of one folder per service and one file per action.
project-name
├── src
│ ├── index.ts
│ ├── rolldown.config.ts
│ ├── moleculer.config.ts
│ ├── utils
│ └── services
│ ├── auth
│ │ ├── index.ts
│ │ └── actions
│ │ ├── signin.ts
│ │ └── signup.ts
│ └── ...
├── package.json
└── tsconfig.json
Using
@ObjectIdwithouttransform: trueresults in astring. Note that automatic transformation to a nativeObjectIdmay impact performance.
@Schema({ strict: true })
export class BookParams {
@String()
title: string
@Array({ items: { type: "objectID" }, max: 24 })
tags: string[]
@Number({ positive: true, integer: true })
pages: number
@ObjectId()
file: string
@Boolean({ optional: true, default: false })
hidden?: boolean
}Even though the original library requires using getSchema(Schem), the @Params(Schem) decorator applies it automatically.
For security reasons, always use the @Params decorator to ensure user data validation. The @Authorization decorator is optional. @Rest exposes the action via the API Gateway. The @Visibility decorator controls action accessibility (e.g., published, public, protected, private). See the Moleculer documentation for more details.
Note,
@Actionmust be on top of the class declaration.
@Action("stop")
@Authorization()
@Rest("POST", "/stop")
@Params(SessionStopParams)
@O.Describe("Short summary", "Short description of the action.")
@O.Response(204, "Short description of the response.")
@O.Response(404, "Short description of the response.", Refs.NotFound)
class SessionStop extends DecoratedMixin {
async handler(ctx: AppContext<SessionStopParams>) {
const { id } = ctx.params
const { user } = ctx.meta
const result = await Session.updateOne({
_id: id,
user: user,
state: SessionState.ACTIVE
}, {
$set: { state: SessionState.TERMINATED }
})
.catch(err => {
this.logger.error(err)
return false
})
if (!result || !result.modifiedCount)
return E.NOT_FOUND("Session not found")
await ctx.broadcast("session.updated", { id })
ctx.meta.$statusCode = 204
return
}
}
// Use `.schema` for legacy compa
export default SessionStop.schemaТекущий шаблон предлагает два варианта создания ошибок, которые должны быть заранее объявлены в @utils/Errors.
- E.ERROR_CODE(message?: string, data?: any)
- MakeError(code: string, { message?: string, data?: any })
import { E } from "@utils/Errors"
return E.LIMIT_REACHED("Message", {
expected: { min: 1, max: 20},
recieved: 100
})Либо-же:
import { MakeError } from "@utils/Errors"
MakeError("ERROR_CODE", {
message: "Message",
data: <...>
})