For an API server, I have a set of models, e.g.
Recording <-> Artist
with ManyToMany relation in TypeORM. The relation needs to be defined in both sides models.
In certain routes I am displaying only Recording and in certain routes also Recordings with Artists, using leftJoinAndSelect. E.g. /api/artists and /api/artists_with_recording.
However, the generated documentation always shows Recordings in Artists.
Is there some easy way to modify the swagger output?
I could make different model objects with swagger markup but with more objects in more contexts that could become pretty messy.
After more searching, I found a solution. I can use the OmitType function from NestJS to create simple ad hoc classes for the documentation.
https://docs.nestjs.com/openapi/mapped-types#omit
So for the route /api/artists I do
#Entity()
class Artist {
...
}
class ArtistWithoutRecording extends OmitType(Artist, ['recording'] as const)
In the controller documentation, I include the ArtistWithoutRecording type.
#Get('artists')
#ApiOperation({
summary: 'Get all artists',
description: 'Some description'
})
#ApiOkResponse({ type: ArtistWithoutRecording })
async getArtists() {
return this.artistDao.findMany()
}
Related
I have a DTO class of a user in NestJS.
I am using many validations using class-validator package in order to enforce my logic.
If a field that doesn't exists on the DTO definition, I would like to ignore it and even throw an error.
This is why I was trying to use the 'excludeExtraneousValues' flag.
When I do use it, it ignores all the fields, even the ones that defined in the DTO.
import { ApiPropertyOptional } from '#nestjs/swagger';
import {
IsDefined,
IsEmail,
IsOptional,
IsPhoneNumber,
MaxLength,
ValidateIf,
} from 'class-validator';
export default class UserDTO {
#ApiPropertyOptional()
#MaxLength(254)
#IsEmail()
#IsDefined()
#ValidateIf((object) => object.email || !object.phone_number)
email?: string;
#ApiPropertyOptional()
#MaxLength(15)
#IsPhoneNumber()
#IsDefined()
#ValidateIf((object) => object.phone_number || !object.email)
phone_number?: string;
#ApiPropertyOptional()
#IsOptional()
#MaxLength(40)
name?: string;
}
As I mentioned, I am using NestJS.
This is the ValidationPipe definition:
app.useGlobalPipes(
new ValidationPipe({
transform: true,
stopAtFirstError: true,
transformOptions: { excludeExtraneousValues: true },
}),
);
Following the addition of 'excludeExtraneousValues' flag, I cannot send any value, even the ones that is defined.
Is it a bug or am I missing something?
A bit old but I stumbled across and can see it was upvoted, so here goes:
I am wondering if you are mixed up on input vs. output. Your question isn't quite specific enough for me to be 100% sure. NestJS apps often make use of the class-validator and class-transformer libraries on both ends of the equation and can work on DTO's/entities that are dressed up with decorators from these libraries.
Pipes such as your ValidationPipe are geared to the input side of things. Refer to the docs: https://docs.nestjs.com/techniques/validation.
Generally class-validator is the key player on the input side, with class-transformer only playing a role with applicable decorators like #Transform(). To make use of class-transformer, you need to pass the transform: true option as you do.
If you want to control behaviours regarding data fields coming into your ValidationPipe that are not defined in your entity/DTO classes, take a look at the whitelist: boolean, forbidNonWhitelisted: boolean, and forbidUnknownValues: true configuration options to satisfy your needs on the input side of things.
I have a feeling that once you check these options out, you will find that you will want to delete the transformOptions: { excludeExtraneousValues: true } option.
On the output side of things is where interceptors come into play. Often in NestJS projects the included ClassSerializerInterceptor is employed to serialize instances of DTO/entity classes to JSON for a response. Docs: https://docs.nestjs.com/techniques/serialization
This side is where you see more frequent use of class-transformer's decorators such as Exclude(), Expose(), and Transform().
This is also where you are probably more likely to find a use-case for a configuration option like excludeExtraneousValues.
Suppose you had data in an object (e.g. from a database, file, or wherever) and wanted to populate a class (such as a DTO) so you could take advantage of these decorators, but only wanted to have certain fields actually sent back in the response, i.e. the ones that you explicitly specified and decorated in your class, and you didn't want to bother with Exclude() and Expose() decorators all over the place.
In such a case, the options for class-transformer may come in handy depending on what you want to do, e.g.
app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector), { excludeExtraneousValues: true }))
I have the follwoing code from tutorial|:
constructor(#InjectModel('User') private readonly userModel: Model<User>) {}
Where User is:
export interface User extends Document {
readonly name: string;
readonly age: number;
readonly phone: string;
}
Could you explain how #InjectModel works, what is 'User' and why we passed Model<User>, what does it mean?
What I can inject also using #InjectModel?
All right, to get into this, first we have to take to truth that interfaces do not exist at runtime. So the User interface you have is only useful during development. I'll try to break this down step by step, starting from the end of the line and working backwards.
Model<User>: Model is an interface type exposed by mongoose that allows us to know that the model we're using has methods like find and create. By saying Model<User> we are saying "This is a mongoose model object that refers to the User interface. This is especially useful for Typescript because as the functions are typed with generics, it knows what the return of methods like find are: an array of User objects. The model interface is really Model<T> where T is the interface that extends Document (another mongoose type).
What is 'User': 'User' is the string equivalent of the name of the interface. If your interface that extends Document is called Dog you use 'Dog', if it's Animal you use 'Animal'. The reason for not passing the interface is because interfaces do not exist at runtime (unlike classes).
How does #InjectModel() work: Okay, the really fun part of the question to answer. Nest works normally by using Injection Tokens. Normally, these tokens are determined by the type of the injected value. In your case Model<User>. Now, the problem here is that A) interfaces don't exist at runtime and B) Typescript does not reflect generics well, so even if Model was a class, all that could be gotten would be Model which isn't enough information on what to inject. So the next logical step Nest takes is to allow a user to provide injection tokens and use the #Inject() decorator. You can do things like injecting an object this way (like package configuration information). Useful, but a bit hard to work with without building your own providers. Now steps in #InjectModel(). #InjectModel() builds an injection token based on the string that's passed into the function. This token is something along the lines of typeModel where type is actually what you pass into the function. This tells Nest specifically what model we are injecting. This also needs to align with the provider created with MongooseModule.forFeature(), hence why name and the value passed to #InjectModel() need to be aligned. Usually it's easiest to align when they use the same string name as the interface.
I need to define the schema to be used by the Application Entity according to user logged in the system.
My idea was to create some kind of decorator to use as a value in the schema property in the entity.
Exemple:
#Entity({name: 'teste', schema: #Schema()})
export class TesteEntity {
#PrimaryGeneratedColumn()
id: number;
#Column()
nome: string;
}
In this entity what represents the value of the schema is the decorator #schema.
But using custom decorators featured in the nestjs documentation didn't help me, if anyone has any ideas on how I can develop this or something better.
I'm using
nodejs
nestjs
typeorm
Context
I have a GraphQL API and a NodeJS & Angular application with a MongoDB database that holds users. For each user, there is a public page with public information like id and username. When a user is logged in, there is a private profile page with extended information like an email.
Just for context, I'm using jsonwebtoken with accesscontrol to authenticate and authorize a user. The information is stored on the Context of every GraphQL resolve function, so whatever is needed to identify a logged in user is available.
I have a GraphQL query that retrieves a public user like so:
query getUserById($id: ID!) {
getUserById(id: $id) {
id,
username
}
}
I am trying to think of the proper implementation to retrieve either a public or a private user. Since GraphQL is strong typed, I'm having some trouble coming up with a proper solution.
Question
How do I implement the distinction between a public and a private user?
Considerations
1. Separate query
So one of the options is to have a seperate query for both public and private fields:
public query
query getUserById($id: ID!) {
getUserById(id: $id) {
id,
username
}
}
private query
query getMe {
getMe {
id,
username,
email
}
}
2. Using GraphQL Interfaces
I came across this Medium article that explains how GraphQL Interfaces are used to return different Types based on a resolveType function. So I would go something like so:
query getUser($id: ID!) {
getUser(id: $id) {
... on UserPrivate {
id,
username
}
... on UserPublic {
id,
username,
email
}
}
}
I have not came across a proper solution and I'm unsure about either of the consideration I have so far.
Any help is much appreciated!
I think what you are missing here is that in GraphQL you usually want to create this deeply connected graph structure. While getUserByIdand getMe work well as entry points (and I think they are still a great idea even with the interface type), you will most likely have user types coming up all over you schema. Imagine the popular blog post example:
type Post {
id: ID!
title: String!
content: String!
author: User!
}
Adding two author fields here does not really work very well. Similarly in your example you might not know that the profile page is your own until you get a response from the backend (think about twitter profiles).
Instead, in my opinion there are two methods to consider:
First one is the interface idea. You would have an interface that has all the common fields and concrete implementations for the private and public type. The nice thing here: If you only use the common fields you don't even have to use the type matching:
query getUser($id: ID!) {
getUser(id: $id) {
id
username
# if you need a private field you can branch off here
... on UserPrivate {
email
}
}
}
When it gets more finely grained (people share what they want to expose to the public, imagine Facebook) or you have a lot of types (UserMe, UserFriend, UserStranger) you might want to consider nullable fields instead. If you don't have access to the field you will receive null from the API. To reduce the amount of null checking you can easily bundle fields into their own types (e.g. Address).
Summary:
From the API point it is a bit easier to return nullable fields because it gives you a lot of flexibility. It is much easier to evolve the second option without breaking changes than the first one. Using interfaces is more expressive and surely more fun to work with in the frontend if you work with static types (Typescript, Flow, Scala.js, Reason, etc.). Keyword: Pattern matching.
I'm planning to organize my controllers in sails using subfolder but I'm not sure how to do it. When I tried using like admin/PageController.js and connect it with the route I keep getting a 404 error.
You can definitely do this. The trick is, the controller identity is its path, in your case admin/PageController. So a custom route in config/routes.js would be something like:
'GET /admin/page/foo': 'admin/PageController.foo'
The great thing is, automatic actions still work, so if you have an index action in the controller then browsing to /admin/page will automatically run it.
You can also create controllers like this with sails generate controller admin/page.
Edit
Since commit 8e57d61 you can do this to get blueprint routes and functionality on nested controllers, assuming there is an AdminPage model in your project:
// api/controllers/admin/PageController.js
module.exports = {
_config: {
model: 'adminpage'
}
}
or this:
// config/routes.js
module.exports.routes = {
'admin/page': {
model: 'adminpage'
}
}
Old Answer
Your options
Defining explicit routes to your grouped controllers in config/routes.js.
Look at Scott Gress' answer for more details.
(If you are a bit adventurous) As i had the exact same requirement for a project of mine I created a Pull Request on Sails that allows you to override the model - controller association. You could install it via
npm install -g git://github.com/marionebl/sails.git#override-controller-model
Assuming it is the api/models/Page.js model you want the blueprint methods for on api/controllers/admin/PageController.js you then could do:
// api/controllers/admin/PageController.js
...
module.exports = {
_config: {
model: 'page'
}
}
Explanation
While generating/creating grouped controllers like this is perfectly valid an possible, you will not get the default blueprint routes you'd expect for controllers accompanied by models with the same identity.
E.g. api/controllers/UserController.js and api/models/User.js share the same identity user, thus the blueprint routes are mounted if they are enabled in config/blueprints.js.
In fact at the moment it is not possible to group models into subfolders in a valid way. This means you won't be able to create a model that matches the identity admin/page of your controller api/controllers/admin/PageController.js - the blueprint routes are not mounted for PageController.
The source responsible for this behavior can be inspected on Github.
I made a diagram that shows how implicit routes, explicit policies, nested controllers, singular models and nested views are related. It does not show an overridden model-controller association as described by #marionebl.
It was mostly an exercise for me to understand this topic better, but I hope it helps somebody else too. Please let me know if I made any mistakes:
Thanks merionebl, its work fine for me and I want to share with all guys my answer derived from merionebl answer.
/config/routes.js
'get /admin/user' : {
controller: "Admin/UserController", action: "find",
model : 'user',
},
My aim is not repeat answer just have upgrade and clear example.
Thanks