-
Notifications
You must be signed in to change notification settings - Fork 391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Request: support class-based request definitions #122
Comments
As a side-note, it would also be nice if the |
sounds as a very good feature request |
Can you show more advanced example which takes full benefit of class-based requests? import {JsonController, Param, QueryParam, Get} from "routing-controllers";
@JsonController()
export class UserController {
@Get("/users")
getAll(
@QueryParam("search")
search: string,
@QueryParam("skip")
skip: number,
@QueryParam("limit")
limit: number,
) {
return userRepository.findAll({ ...arguments });
}
@Get("/users/:id")
getOne(
@Param("id")
id: number,
) {
return userRepository.findById(id);
}
} Only I can see is the usage with
Nope, currently method parameters names are not reflected by TypeScript. But class properties are - see how class-based entities works in TypeORM. So it's the second gain from this feature 😉 |
@19majkel94 yea it's not so much about syntax or readability, though I think there is some benefit there. As you mentioned it also plays nicely into @Get('/:customer/users')
@Returns({ type: User, array: true })
export class GetAllUsers {
@Param()
customer: string;
@QueryParam()
search: string;
@QueryParam("skip")
from: number;
@QueryParam("limit")
size: number;
@QueryParam()
@IsArray()
@IsNumber({ each: true })
flags: number[];
} Take the above example and assume it's exposed in a shared library between client and server. Using the metadata emitted by the TS compiler, it would be possible to write a generic REST client implementation that could interact with any const client = new RoutingClient();
client.send(new GetAllUsers({
customer: 'my-company',
search: 'query',
size: 50,
flags: [1,2]
}))
.then(users: User[] => {
// -> GET /my-company/users?search=query&size=50&flags=1&flags=2
// client automatically:
// - knew how to serialize request parameters
// - validated request params pre-fetch
// - built appropriate request URL
// - knew how to interpret server response
// - deserialized response into appropriate type
}) I'm making a lot of assumptions in this example, but I hope it gets my point across that there's a lot of powerful things we could do with the metadata from class-based request definitions. |
right this feature allow to setup communication easily. Sending all params as separate query params is a big pain. @marshall007 what I do in my apps I usually have lets say export class QuestionFilter {
limit: number;
offset: number;
sort: "name"|"date";
/// .... other properties, methods if needed
} and then in the controller @Get("/questions")
all(@QueryParam("filter") filter: QuestionFilter) { // filter is automatically casted to QuestionFilter
/// ...
} and the request is: |
That's the point. All looks beautiful with simple&clean examples covering 80% of cases. But the problem is with more complicated examples like with authorization: @Get('/:customer/users')
@Returns({ type: User, array: true })
export class GetAllUsers {
@JWT()
customer: User;
@QueryParam()
search: string;
@Req()
req: Request;
} Request object from the viewpoint of controller should have a authenticated user object extracted from jwt from header. So if you want to reuse class definition, you would have to explicitly pass the object in So you can't reuse definition as it is, you have to parse it an generate ClientRequest definition based on backend request model. E.g. when see
As @pleerock showed, we can easily validate query params with I'm not saying that this feature is bad - it looks good but all use cases can be done now nearly as easy, so this might just go to |
@19majkel94 that's probably not how I would model those particular scenarios. Things like authentication are generally handled at a higher level and shouldn't really be modeled at the individual request level anyway. There's no reason you can't still inject additional parameters in your controller method the traditional way: export class GetUserRequest {
@Param("id")
id: number;
}
@JsonController()
export class UserController {
@Get("/users/:id")
getOne(@Model() req: GetUserRequest, @JWT() jwt: Token, @Req() request: Request) {
return userRepository.findById(req.id);
}
} |
I know that I can inject additional parameters. But with things like So basically export class ReqRouteParams {
@IsMongoId()
userId: string;
@IsMongoId()
postId: string;
}
export class ReqQueryParams {
@IsInt()
@IsPositive()
limit: number;
@IsInt()
@IsPositive()
offset: number;
}
@JsonController()
export class SampleController {
@Get("/users/:userId/posts/:postId/coments")
async action(
@Params() param: ReqRouteParams,
@QueryParams() query: ReqQueryParams,
@JWT() jwt: Token,
@Req() request: Request,
) {
return commentsRepository.findByPost({ id: param.postId }, query);
}
} But let's close this discussion for now - it's not the case of this issue, we will discuss is later in TypeStack as it would be a part of this framework and require more integration with |
@19majkel94 is right that its just a nice-to-have feature and you can do same almost same way. But I really like this feature because its a bit more elegant and beauty, and for those who have lot of params in request and don't prefer to use filter pattern as I showed, it can be a useful feature. I think it can be added to routing-controllers. |
We have |
nope, lets left it open and wait if someone want to contribute, maybe even @marshall007 |
I think the OP's request is a good idea, but I think it would be even better (and more flexible) to implement reusable and combinable export class FilterParams {
@QueryParam("genre")
genre: string;
@QueryParam("year")
year: number;
}
export class PaginationParams {
@QueryParam("pageNumber")
pageNumber: number;
@QueryParam("pageSize")
pageSize: number;
} export class MovieController {
@Get("/movies")
listMovies(
@ParamGroup() filters: FilterParams,
@ParamGroup() pagination: PaginationParams,
) {
// Select with filter and pagination here
}
} This way, filter params could be reused across endpoints where they make sense, and pagination params could also be reused separately across many other endpoints that use different filters. Although the above code example doesn't show it, a |
Query params as class can be usable, when all get params are inherited from some shared model or for example when defining fields swagger. I've got such case now. Maybe I could help with PR? We just need to discuss how it should work finally. |
Stale issue message |
It would be nice to be able to declare the structure of your requests in an external class. Doing so would also open up the possibility of providing client-side tooling for building requests based on shared class definitions.
Example
The text was updated successfully, but these errors were encountered: