A GraphQL operation can be either read or write, however, Query is used to read or fetch values, whereas Mutation is used to write or post values.
Since you want to get data about authors and books, you need to query the author and book classes. To make the author & book class in GraphQL queryable, you should create a new type and extend it from custom ObjectGraphType<T>.
- public class AuthorType:ObjectGraphType<Author>
- {
- public AuthorType(IBookRepository bookRepository)
- {
- Field(x => x.AuthorId).Description("Author Id");
- Field(x => x.FirstName).Description("Author's first name");
- Field(x => x.LastName);
- Field<ListGraphType<BookType>>("books",
- arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "authorId" }),
- resolve: context =>
- {
- return bookRepository.GetBooks(context.Source.AuthorId);
- });
- }
- }
- public class BookType:ObjectGraphType<Book>
- {
- public BookType()
- {
- Field(x => x.BookId);
- Field(x => x.Title);
- Field(x => x.PublicationYear);
- }
- }
You can now write a GraphQL query that will handle fetching an author or list of authors.
- public class AuthorQuery:ObjectGraphType<object>
- {
- public AuthorQuery(IAuthorRepository authorRepository)
- {
- Field<ListGraphType<AuthorType>>("authors", resolve:context =>
- {
- return authorRepository.GetAuthorsAsync();
- });
-
- Field<AuthorType>("author",
- arguments: new QueryArguments(new QueryArgument<IntGraphType> { Name = "authorId" }),
- resolve: context =>
- {
- return authorRepository.GetAuthorAsync(context.GetArgument<int>("authorId"));
- });
- }
- }
Similarly, you can write a GraphQL query that will handle fetching a book or list of books.
- public class BookQuery:ObjectGraphType
- {
- public BookQuery(IBookRepository bookRepository)
- {
- Field<ListGraphType<BookType>>("books", resolve: context =>
- {
- return bookRepository.GetBooksAsync();
- });
- }
- }
A GraphQL Schema is at the core of Server implementation. The Schema is written in Graph Schema language and it can be used to define object types and fields to represent data that can be retrieved from API.
- public class AuthorSchema:Schema
- {
- public AuthorSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<AuthorQuery>();
- }
- }
- public class BookSchema:Schema
- {
- public BookSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<BookQuery>();
- }
- }
Add dependency injection for author and book schema in ConfigureService method of Startup class.
- services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
- services.AddScoped<AuthorSchema>();
- services.AddScoped<BookSchema>();
- services.AddGraphQL(o => o.ExposeExceptions = false)
- .AddGraphTypes(ServiceLifetime.Scoped);
-
- services.Configure<KestrelServerOptions>(options =>
- {
- options.AllowSynchronousIO = true;
- });
Now, run the application and open the playground UI. You can use the following queries to run author and book details from the data.
- query {
- author(authorId:1){
- firstName
- lastName
- authorId
- books {
- publicationYear
- title
- bookId
- }
- }
- }
Remember one of the pros of GraphQL; over-fetching and under-fetching. In the above query, we can retrieve any types defined in author and book types.
GraphQL Mutation
As discussed before, Mutation is used to write or post values. You have to define InputObjectGraphType for author and book types.
- public class AuthorInputType: InputObjectGraphType
- {
- public AuthorInputType()
- {
- Name = "authorInput";
- Field<NonNullGraphType<StringGraphType>>("firstName");
- Field<NonNullGraphType<StringGraphType>>("lastName");
- }
- }
- public class BookInputType:InputObjectGraphType
- {
- public BookInputType()
- {
- Name = "bookInput";
- Field<NonNullGraphType<StringGraphType>>("title");
- Field<NonNullGraphType<IntGraphType>>("publicationYear");
- Field<IntGraphType>("authorId");
- }
- }
You can define mutation for author and book using ObjectGraphType
- public class AuthorMutation:ObjectGraphType
- {
- public AuthorMutation(IAuthorRepository authorRepository,IBookRepository bookRepository)
- {
- Field<AuthorType>("insertAuthor",
- arguments: new QueryArguments(new QueryArgument<AuthorInputType> { Name = "author" }),
- resolve:context=>
- {
- return authorRepository.InsertAuthorAsync(context.GetArgument<Author>("author"));
- });
- }
- }
- public class BookMutation:ObjectGraphType
- {
- private readonly IBookRepository bookRepository;
- private readonly IAuthorRepository authorRepository;
-
- public BookMutation(IBookRepository bookRepository,IAuthorRepository authorRepository)
- {
- this.bookRepository = bookRepository;
- this.authorRepository = authorRepository;
-
- Field<BookType>("insertAuthorAndBook",
- arguments: new QueryArguments(
- new QueryArgument<AuthorInputType> { Name = "author" },
- new QueryArgument<BookInputType> { Name = "book" }),
- resolve: context =>
- {
- var author = context.GetArgument<Author>("author");
- var book = context.GetArgument<Book>("book");
- return InsertBook(author, book);
- });
-
- Field<BookType>("insertBook",
- arguments: new QueryArguments(new QueryArgument<BookInputType> { Name = "book" }),
- resolve: context =>
- {
- return bookRepository.InsertBook(context.GetArgument<Book>("book"));
- });
- }
-
- private async Task<Book> InsertBook(Author author,Book book)
- {
- if (author == null)
- return null;
-
- var existingAuthor =await authorRepository.GetAuthorByFirstNameAsync(author.FirstName);
- if (existingAuthor == null)
- {
- var newAuthor = await authorRepository.InsertAuthorAsync(author);
- book.AuthorId = newAuthor.AuthorId;
- return await bookRepository.InsertBook(book);
- }
- else
- {
- book.AuthorId = existingAuthor.AuthorId;
- return await bookRepository.InsertBook(book);
- }
- }
- }
Finally, you need to define mutation in both the author and book schema classes
- public class AuthorSchema:Schema
- {
- public AuthorSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<AuthorQuery>();
- Mutation = resolver.Resolve<AuthorMutation>();
- }
- }
- public class BookSchema:Schema
- {
- public BookSchema(IDependencyResolver resolver):base(resolver)
- {
- Query = resolver.Resolve<BookQuery>();
- Mutation = resolver.Resolve<BookMutation>();
- }
- }
You can use the following query to insert a record
- mutation($author:authorInput!,$book:bookInput!){
- insertAuthor(author:$author){
- firstName
- lastName
- }
- insertBook(book:$book){
- title,
- publicationYear
- }
- }
You need an input variable for both author and book
- {
- "author":{
- "firstName":"Charles",
- "lastName":"Darwin"
- },
- "book": {
- "title": "On the origin of Species",
- "publicationyear": 1856
- }
- }
Everything seems to be working fine, but there are still issues with GraphQL endpoint. GraphQL should have a single endpoint and currently, there are 2 endpoints for author and book.
In the next article, we will discuss fixing the endpoint issue by introducing GraphQLController and will also see how the client has to invoke the API.
I hope you liked the article. In case you find the article interesting, please kindly like and share it.