nitrogql:model
plugin
nitrogql:model-plugin
is a built-in plugin for enhancing generated resolver types. It helps you to define which fields use default resolvers. Without this plugin, you have to define all resolvers by yourself, which is not practically possible.
Usage
To use the plugin, you need to add it to the plugins array in the configuration file.
schema: ./schema/*.graphql
extensions:
nitrogql:
plugins:
- "nitrogql:model-plugin"
# ...
@model
directive definition
The plugin adds a @model
directive with the following definition.
directive @model(
type: String
) on OBJECT | FIELD_DEFINITION
This directive is relevant when you are using generated resolver types.
Using @model directive on fields
An example usage of the @model
directive is:
type Query {
me: User!
}
type User {
id: ID! @model
name: String! @model
email: String!
posts: [Post!]!
}
Fields marked with @model
directive are considered as part of the model, which means that they are included in data interchanged between resolvers.
Concretely, resolvers that return a User
must return an object containing id
and name
fields. Also, resolvers for User
's fields receive an object with those fields as the first argument.
Also as a consequence, resolvers for model fields need not be defined. The default resolver should be able to pick up the appropriate value from the model object.
With the above setting, the following resolvers implementation is considered valid in terms of type safety.
const queryResolvers: Resolvers<Context>["Query"] = {
me: async () => {
// returns logged in user
return {
id: "1234",
name: "John Smith",
};
}
};
const userResolvers: Resolvers<Context>["User"] = {
// no need to define resolvers for id and name
email: async (user) => {
const userFromDB = await db.findUserById(user.id);
return userFromDB.email;
},
posts: async (user) => {
const postsFromDB = await db.findPostsByUserId(user.id);
return postsFromDB;
}
};
Using @model directive on object types
Instead of marking fields with @model
, you can apply the @model
directive to the whole object type. Doing so will replace the model type with one you specify in the type
argument. The type
argument accepts any string that is a valid TypeScript code that represents a type.
If you use @model
this way, you must define resolvers for all fields of the object type.
An example usage of the @model
directive is:
type Query {
me: User!
}
type User @model(type: "import('@/models/user').User") {
id: ID!
name: String!
email: String!
posts: [Post!]!
}
With the above setting, you would implement resolvers like the following.
import { User } from "@/models/user";
const queryResolvers: Resolvers<Context>["Query"] = {
me: async () => {
// returns logged in user
return new User("1234", "John doe");
}
};
const userResolvers: Resolvers<Context>["User"] = {
id: (user) => {
return user.id;
},
name: (user) => {
return user.name;
},
email: async (user) => {
// user is an instance of the User class
return await user.getEmail();
},
posts: async (user) => {
return await user.getPosts();
}
};
Effects on Runtime Schema
The @model
directive is purely for type definition generation. Therefore, it is removed from the runtime schema (one generated by using the generate.serverGraphqlOutput option).
🧺 See Also: the blog post for the 1.1 release also explains the @model
directive and its usage in detail.