How to define a constant in the swagger definition of a NestJS Class? - nestjs

I have a bunch of Exceptions that use the same interface, so it is easier for the frontend to distinct between them from the same endpoint.
I want to be able to document in swagger that MyWeirdExpection has a property type that has the value my-weird-exception (it comes from an string ENUM if that is relevant).
Is this possible to do?
Something like:
enum MyEnum {
MyWeirdExpection: 'my-weird-exception',
RegularExpection: 'my-regular-expection'
... more
}
#ApiProperty({
description: 'Frontend can react on this property',
type: String, // <-----------WHAT HERE?
})
type: MyEnum.MyWeirdExpection;
Again, my goal is that in the swagger definition, says that MyWeirdExpection.type is the constant 'my-weird-exception'

The following will provide a type of MyEnum.MyWeirdExpection with the value populated as 'my-weird-exception':
#ApiProperty({
description: "Search by asset type",
required: false,
default: MyEnum.MyWeirdExpection,
type: MyEnum.MyWeirdExpection
})
assetType4: string | undefined;

Related

Typegoose enum arrayProp returns an error: Type is not a constructor

I have got a problem with the definition of my array schema. So basically, what I wanted to achieve is a single user Model with a property called games, that holds an array of games user is playing. The problem is I have got defined games as enum:
module Constants {
export enum Games {
LOL = 'League Of Legends',
}
}
export {Constants};
And now, when I try to attach it to the schema model like that:
#arrayProp({ required: true, items: Constants.Games })
games: Constants.Games[];
I receive an error (after a successful compilation, just before the server start)
^ const instance = new Type();
TypeError: Type is not a constructor
at baseProp (C:\Users\Borys\Desktop\lft\backend\node_modules\typegoose\lib\prop.js:114:22)
at C:\Users\Borys\Desktop\lft\backend\node_modules\typegoose\lib\prop.js:177:9
at DecorateProperty (C:\Users\Borys\Desktop\lft\backend\node_modules\reflect-metadata\Reflect.js:553:33)
at Object.decorate (C:\Users\Borys\Desktop\lft\backend\node_modules\reflect-metadata\Reflect.js:123:24)
at __decorate (C:\Users\Borys\Desktop\lft\backend\build\dataModel\User.js:4:92)
at Object.<anonymous> (C:\Users\Borys\Desktop\lft\backend\build\dataModel\User.js:64:1)
I have read a little bit about this error, but it relates to the required option for items/itemsRef I tried removing required, using enum, using even itemsRef and relating to the different set of documents but none of these worked for me.
Anyone could help/relate?
The problem is, you cannot use enums as an runtime mongoose type, so i would recommend using
#prop({ required: true, type: String, enum: Constants.Games })
games: Constants.Games[];
type for setting the type (which is string) (this option can be omitted - thanks to reflection)
enum for setting an enum to validate against
#arrayProp({ required: true, items: String })
games: Constants.Games[];
is a solution to this problem.
I would appreciate it if anyone could clarify and tell me more about why shouldn't I use enum in the items property.

How to change query parameters serialization using nestjs/swagger?

Recently I have updated nestjs/swagger package in my project to ^4.0.0. Previously Swagger serialized my query parameters as follows:
/resources?parameter=1,2,3
Now it looks like this:
/resources?parameter=1&parameter=2&parameter=3
DTO object for my query looks like this:
class QueryDTO {
#ApiProperty({
required: false,
type: [Number],
})
#IsOptional()
readonly parameter?: number[];
}
How can I change this behaviour?
I am on nestjs/swagger 4.5.9
I made it work by define the DTO ( notice the format: 'form')
#IsNotEmpty()
#ApiProperty({
type: [Number],
format: 'form',
})
#IsArray()
#Transform((value: string) => value.split(',').map(item => Number(item)))
#IsNumber({}, {each: true})
deviceId: Array<number>;
As a workaround, you can remove the #ApiProperty from the DTO and use the #ApiQuery decorator on the controller method which has the style and explode options(just keep the same parameter name as the dto property)
#Get('resources')
#ApiQuery({
name: 'parameter',
required: false,
explode: false,
type: Number,
isArray: true
})
getResources(#Query('parameter') parameter?: number[]) {}
You can still use the DTO object as it is for additional parameters that work the usual way.

NodeJS/GraphQL: Trying to use a custom DateTime scalar and I'm getting an error

I'm following a tutorial where the teacher is using a String type for his createdAt fields and suggested that if we wanted, to use a custom scalar type for a stronger typed DateTime field so I'm trying to do just that.
I'm getting the following error: Error: Unknown type "GraphQLDateTime".
Here is the offending code:
const { gql } = require('apollo-server')
const { GraphQLDateTime } = require('graphql-iso-date')
module.exports = gql`
type Post {
id: ID!
username: String!
body: String!
createdAt: GraphQLDateTime!
}
type User {
id: ID!
email: String!
token: String!
username: String!
createdAt: GraphQLDateTime!
}
input RegisterInput {
username: String!
password: String!
confirmPassword: String!
email: String!
}
type Query {
getPosts: [Post]
getPost(postId: ID!): Post
}
type Mutation {
register(registerInput: RegisterInput): User
login(username: String!, password: String!): User!
createPost(body: String!): Post!
deletePost(postId: ID!): String!
}
`
I have added the graphql-iso-date library and VSCode's intellisense is picking that up so I know that's not the issue. It's also indicating that GraphQLDateTime is not being used anywhere in the file even though I'm referencing it.
I know this is probably an easy fix but I'm still new to NodeJS and GraphQL in the context of NodeJS. Any idea what I'm doing wrong? Also is there another DateTime scalar that might be more preferable (Best practices are always a good idea.) Thanks!
There's two steps to adding a custom scalar using apollo-server or graphql-tools:
Add the scalar definition to your type definitions:
scalar DateTime
Add the actual GraphQLScalar to your resolver map:
const { GraphQLDateTime } = require('graphql-iso-date')
const resolvers = {
/* your other resolvers */
DateTime: GraphQLDateTime,
}
Note that the key in the resolver map (here I used DateTime) has to match whatever you used as the scalar name in Step 1. This will also be the name you use in your type definitions. The name itself is arbitrary, but it needs to match.
See the docs for more details.

GraphQL: How nested to make schema?

This past year I converted an application to use Graphql. Its been great so far, during the conversion I essentially ported all my services that backed my REST endpoints to back grapqhl queries and mutations. The app is working well but would like to continue to evolve my object graph.
Lets consider I have the following relationships.
User -> Team -> Boards -> Lists -> Cards -> Comments
I currently have two different nested schema: User -> team:
type User {
id: ID!
email: String!
role: String!
name: String!
resetPasswordToken: String
team: Team!
lastActiveAt: Date
}
type Team {
id: ID!
inviteToken: String!
owner: String!
name: String!
archived: Boolean!
members: [String]
}
Then I have Boards -> Lists -> Cards -> Comments
type Board {
id: ID!
name: String!
teamId: String!
lists: [List]
createdAt: Date
updatedAt: Date
}
type List {
id: ID!
name: String!
order: Int!
description: String
backgroundColor: String
cardColor: String
archived: Boolean
boardId: String!
ownerId: String!
teamId: String!
cards: [Card]
}
type Card {
id: ID!
text: String!
order: Int
groupCards: [Card]
type: String
backgroundColor: String
votes: [String]
boardId: String
listId: String
ownerId: String
teamId: String!
comments: [Comment]
createdAt: Date
updatedAt: Date
}
type Comment {
id: ID!
text: String!
archived: Boolean
boardId: String!
ownerId: String
teamId: String!
cardId: String!
createdAt: Date
updatedAt: Date
}
Which works great. But I'm curious how nested I can truly make my schema. If I added the rest to make the graph complete:
type Team {
id: ID!
inviteToken: String!
owner: String!
name: String!
archived: Boolean!
members: [String]
**boards: [Board]**
}
This would achieve a much much deeper graph. However I worried how much complicated mutations would be. Specifically for the board schema downwards I need to publish subscription updates for all actions. Which if I add a comment, publish the entire board update is incredibly inefficient. While built a subscription logic for each create/update of every nested schema seems like a ton of code to achieve something simple.
Any thoughts on what the right depth is in object graphs? With keeping in mind the every object beside a user needs to be broadcast to multiple users.
Thanks
GraphQL's purpose is to avoid a couple of queries, so I'm sure that making the nested structure is the right way. With security in mind, add some GraphQL depth limit libraries.
GraphQL style guides suggest you have all complex structures in separate Object Types ( as you have, Comment, Team, Board... ).
Then making a complex query/mutation is up to you.
I'd like you to expand this sentence
Which if I add a comment, publish the entire board update is
incredibly inefficient
I'm not sure about this as you have your id of the Card. So adding new comment will trigger mutation which will create new Comment record and update Card with the new comment.
So your structure of data on the backend will define the way you fetch it but not so much the way you mutate it.
Take a look at the GitHub GraphQL API for example:
each of the mutations is a small function for updating/creating piece of the complex tree even if they have nested structure of types on the backend.
In addition for general knowledge of what are approaches for designing the mutations, I'd suggest this article.
You can use nesting in GraphQL like
type NestedObject {
title: String
content: String
}
type MainObject {
id: ID!
myObject: [NestedObject]
}
In the above code, the type definition of NestObject gets injected into the myObject array. To understand better you can see it as:
type MainObject {
id: ID!
myobject: [
{
title: String
content: String
}
]
}
I Hope this solves your problem!

Using TypeScript enum with mongoose schema

I have a schema with an enum:
export interface IGameMapModel extends IGameMap, Document {}
export const gameMapSchema: Schema = new Schema({
name: { type: String, index: { unique: true }, required: true },
type: { type: String, enum: CUtility.enumToArray(GameMode) }
});
export const GameMap: Model<IGameMapModel> = model<IGameMapModel>('GameMap', gameMapSchema);
The GameMap is an enum.
First problem is already in here: I need to convert the enum to a string array in order to use it with the schema.
Secondly, I wanna use an enum value directly during the schema creation.
new GameMap({
name: 'Test',
type: GameMode.ASSAULT
});
returns ValidationError: type: '1' is not a valid enum value for path 'type'.
I am not sure whether this can actually work due to the string array I set in the model enum property.
My idea would be to create some kind of type conversion during the schema creation. Does this work with mongoose or would I have to create some kind of helper for object creation?
GameMode.ASSAULT is evaluating as it's numeric value, but GameMode is expecting the type to be a string. What are you expecting the string evaluation to be? If you need the string value of the enum, you can access it with GameMode[GameMode.ASSAULT], which would return ASSAULT as a string.
For example:
enum TEST {
test1 = 1,
test2 = 2
}
console.log(TEST[TEST.test1]);
//Prints "test1"
From the Mongoose docs on validation, in schema properties with a type of String that have enum validation, the enum that mongoose expects in an array of strings.
This means that CUtility.enumToArray(GameMode) needs to either return to you an array of the indexes as strings, or an array of the text/string values of the enum--whichever you are expecting to store in your DB.
The validation error seems to imply that 1 is not contained within the array that is being produced by CUtility.enumToArray(GameMode), or the validation is seeing GameMode.ASSAULT as a number when it is expected a string representation of 1. You might have to convert the enum value you are passing in into a string.
What is the output of CUtility.enumToArray(GameMode)? That should help you determine which of the two is your problem.
Why don't you just create custom getter/setter:
const schema = new Schema ({
enumProp: {
type: Schema.Types.String,
enum: enumKeys(EnumType),
get: (enumValue: string) => EnumType[enumValue as keyof typeof EnumType],
set: (enumValue: EnumType) => EnumType[enumValue],
},
});
EDIT:
Don't forget to explicitly enable the getter
schema.set('toJSON', { getters: true });
// and/or
schema.set('toObject', { getters: true });
This way you can fine-control how exactly you want to represent the prop in the db, backend and frontend (json response).

Resources