I´m adopting GraphQL for new fullstack projects and I´ve already studies many of the concepts and started my first projects.
My question is related to use declarative vs programatically GraphQL schema definition. Basically all I can see in the GraphQL official site uses the declarative approach: you define the schema in one or more files like (thanks to this example here):
type Brand {
name: String
logoUrl: String
}
enum Gender {
MALE
FEMALE
}
type Image {
thumbnailUrl: String
smallUrl: String
mediumUrl: String
largeUrl: String
}
type Article {
id: ID! # non-nullable, is guaranteed to exist on every article
name: String
thumbnailUrl: String
brand: Brand
genders: [Gender]
images: [Image]
remendations: [Article]
}
type Query {
Article(id: ID!): Article
Articles: [Articles]
}
Very clean and concise code even for a somehow plex data structure.
But most of examples I see on web and even on books that I´ve studied use the programatically approach to build the schema, something like:
import { GraphQLObjectType, GraphQLInputObjectType } from 'graphql';
import {GraphQLNonNull, GraphQLID, GraphQLList } from 'graphql';
import { GraphQLString, GraphQLInt, GraphQLBoolean } from 'graphql';
import { UserType } from '../User/types';
import UserModel from '../../../models/User';
const fields = {
_id: {
type: new GraphQLNonNull(GraphQLID)
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
}
};
const CompanyType = new GraphQLObjectType({
name: 'Company',
description: 'Company',
fields: fields
})
const Company = {
type: CompanyType,
description: 'Get single pany',
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
resolve(root, params) {
params.deleted = false;
return CompanyModel.find(params).exec();
}
}
const Companies = {
type: new GraphQLList(CompanyType),
description: 'Get all panies',
resolve(root) {
const panies = CompanyModel.find({ deleted: false }).exec();
if (!panies) {
throw new Error('Error getting panies.')
}
return panies;
}
}
export default {
Company,
Companies
}
My goal is to build a large SaaS application, so the schema will get pretty plex and my worry is that the code gets plex pretty soon.
So, should I go for a declarative approach, a programmatically approach or a mix of two?
What is the best practices here?
I´m adopting GraphQL for new fullstack projects and I´ve already studies many of the concepts and started my first projects.
My question is related to use declarative vs programatically GraphQL schema definition. Basically all I can see in the GraphQL official site uses the declarative approach: you define the schema in one or more files like (thanks to this example here):
type Brand {
name: String
logoUrl: String
}
enum Gender {
MALE
FEMALE
}
type Image {
thumbnailUrl: String
smallUrl: String
mediumUrl: String
largeUrl: String
}
type Article {
id: ID! # non-nullable, is guaranteed to exist on every article
name: String
thumbnailUrl: String
brand: Brand
genders: [Gender]
images: [Image]
remendations: [Article]
}
type Query {
Article(id: ID!): Article
Articles: [Articles]
}
Very clean and concise code even for a somehow plex data structure.
But most of examples I see on web and even on books that I´ve studied use the programatically approach to build the schema, something like:
import { GraphQLObjectType, GraphQLInputObjectType } from 'graphql';
import {GraphQLNonNull, GraphQLID, GraphQLList } from 'graphql';
import { GraphQLString, GraphQLInt, GraphQLBoolean } from 'graphql';
import { UserType } from '../User/types';
import UserModel from '../../../models/User';
const fields = {
_id: {
type: new GraphQLNonNull(GraphQLID)
},
name: {
type: GraphQLString
},
phone: {
type: GraphQLString
}
};
const CompanyType = new GraphQLObjectType({
name: 'Company',
description: 'Company',
fields: fields
})
const Company = {
type: CompanyType,
description: 'Get single pany',
args: {
id: {
name: 'id',
type: new GraphQLNonNull(GraphQLID)
}
},
resolve(root, params) {
params.deleted = false;
return CompanyModel.find(params).exec();
}
}
const Companies = {
type: new GraphQLList(CompanyType),
description: 'Get all panies',
resolve(root) {
const panies = CompanyModel.find({ deleted: false }).exec();
if (!panies) {
throw new Error('Error getting panies.')
}
return panies;
}
}
export default {
Company,
Companies
}
My goal is to build a large SaaS application, so the schema will get pretty plex and my worry is that the code gets plex pretty soon.
So, should I go for a declarative approach, a programmatically approach or a mix of two?
What is the best practices here?
Share Improve this question edited Oct 17, 2017 at 6:03 Cœur 38.8k25 gold badges206 silver badges278 bronze badges asked Jul 29, 2017 at 14:13 MendesMendes 18.6k38 gold badges166 silver badges282 bronze badges4 Answers
Reset to default 6There's a good bit of discussion on this topic here and here.
IMHO, the biggest advantage to defining your schema in GraphQL schema language is the readability. It makes your schema easy to read and understand, especially to internal users who may be querying the endpoint but not actually involved in it's design. I think it also makes defining and changing the schema less error-prone.
On the other hand, defining the schema programatically offers a lot more flexibility. For example, if you use buildSchema
, you're limited to passing in resolvers for just your queries and mutations. This works fine if you're ok with every type just utilizing the default resolver -- but what happens when you need to define resolvers for individual fields?
Defining the schema programatically allows you to define resolvers for individual fields within any of the types you specify. Not only does this help transform the data you're getting back from your database (turning that thumbanail_url
into a thumbnailUrl
field), but if those fields require additional database queries, it prevents them from firing off unless the field is actually requested, which can be a significant performance boost. As the documentation points out, this approach is also useful if you want to generate the schema automatically.
Personally, this is why I love graphql-tools
makeExecutableSchema. It's kind of a middle-of-the-road approach, allowing you to define your types in a very clean way (using GraphQL schema language), while allowing for a good bit of flexibility in implementing resolvers.
You can use merge-graphql-schemas
library for better schema design.
This post demonstrates how to modularize your graphql schemas, using the library and providing a seed project you can clone. Hope it helps! :)
- Declarative == Full-schema (or schema-first).
- Programmatically == Object-based (or code first).
for the post below.
"Completely relying on the full-schema text to create a GraphQL schema has a few drawbacks. You’ll need to put in some effort to make the code modularized and clear, and you’ll have to rely on coding patterns and tools to keep the schema-language text consistent with the tree of resolvers (aka resolvers map). These are solvable problems.
The biggest issue I see with the full-schema method is that you lose some flexibility in your code. All your types have to be written in that specific way that relies on the schema-language text. You can’t use constructors to create some types when you need to. You’re locked into this string-based approach. Although the schema-language text makes your types more readable, in many cases, you’ll need flexibility over readability.
The object-based method is flexible and easier to extend and manage. It does not suffer from any of the problems I just mentioned. Your code will be modular with it because your schema will be a bunch of objects. You also don’t need to merge modules, because these objects are designed and expected to work like a tree.
The only issue I see with the object-based method is that you have to deal with a lot more code around what’s important to manage in your modules (types and resolvers). Many developers see that as noise, and I do not blame them.
If you’re creating a small, well-defined GraphQL service, using the full-schema-string method is probably okay. However, in bigger and more agile projects, I think the more flexible and powerful object-based method is the way to go."
Source
First of all, in 2023, with all the tools that are available for nodejs I would advocate for schema-first over code-first. Good documentation on code-first seems to be harder and harder to find.
Second, Declarative does not mean schema-first. Graphql itself is a declarative language. Your implementation however is not. You are just using a domain specific language to define the types and are still implementing the resolvers imperatively.
You could theoretically create a library that allows declarative schema generation but for this to work it needs to be highly opinionated, removing any need for programming "how to do it".
Lastly, schema-first or code-first shouldn't matter. If your codebase bees so large that you wish you picked one over the other it means you need to split the concerns into separate manageable modules or services.