[DOTNET] GraphQL บน NET8 ด้วย HotChocolate Library #01 (Query)

สำหรับ Blog นี้ เรียกว่าลองทำ GraphQL ด้วย dotnet core 8 ครับ โดยใช้ library HotChocolate ชื่อมันดูน่ากินดีนะ 555 โดยของหวาน Set นี้มี 3 Lib

  • Hot Chocolate - open-source GraphQL server
  • Strawberry Shake - open-source GraphQL client
  • Banana Cake Pop - GraphQL GUI

สำหรับ Blog ช่วงนี้จะเน้นตัว Hot Chocolate ก่อนครับ แล้วค่อยขยับไปส่วนอื่นๆ โดยมีหัวข้อดังนี้

มาเริ่มกันเลย

- Simple REST API (GET)
dotnet new webapi --use-controllers -o GraphQLAPI
  • เพิ่ม Library เข้าไป
    - HotChocolate.AspNetCore ตัว Engine หลักเลยที่จัดการเรื่องนี้
    - HotChocolate.AspNetCore.Playground ตัวสร้าง Web คล้าย Postman ให้เราลอง Test ส่ง Request ได้
  • คำสั่งที่ใช้เพิ่ม Library
dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.AspNetCore.Playground
  • มาวางโครงสร้าง Project กันก่อน จะเป็นตามนี้
GraphQLAPI
-> Controllers
-> GraphQL
---> Query
---> Type
-> Services
-> Infra
---> Models
---> Repositories
  • มาที่ส่วน Infra > Model กันก่อน เราจะลองมาเพิ่มส่วน DTO กันก่อน เอาไว้เก็บข้อมูล ในที่นี้ผมใช้ข้อมูล Suppliers นะ (จริงๆ Copilot) มันแนะนำมา ผมเลยตามเลยครับ
namespace GraphQLAPI.Infra.Model
{
    public class SupplierDTO
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Address { get; set; }
        public string Email { get; set; }
        public string Phone { get; set; }
    }
}
  • ต่อมาเพิ่มส่วนของ Infra > Repository เพิ่ม SupplierRepository ขึ้นมาจัดการ ตอนนี้ผมจะเน้นส่วนยการดึงข้อมูลก่อน เลยเพิ่ม Operation ดึงข้อมูล และให้คืนจากที่ mock ไว้ไปก่อน พวก Add /Edit เดี๋ยวต่อยมาต่อยอดอีก Blog
using GraphQLAPI.Infra.Model;

namespace GraphQLAPI.Infra.Repository
{
    public interface ISupplierRepository
    {
        Task<IEnumerable<SupplierDTO>> GetSuppliers();
        Task<SupplierDTO> GetSupplier(int id);
        // Task<SupplierDTO> AddSupplier(Supplier supplier);
        // Task<SupplierDTO> UpdateSupplier(Supplier supplier);
        // Task<SupplierDTO> DeleteSupplier(int id);
    }
}
namespace GraphQLAPI.Infra.Repository;

using GraphQLAPI.Infra.Model;
using System.Collections.Generic;

public class SupplierRepository: ISupplierRepository
  {
    private readonly IEnumerable <SupplierDTO> suppliers = new List <SupplierDTO>
    {
        new SupplierDTO
        {
            Id = 1,
            FirstName = "PingkungA",
            LastName = "Agnukginp",
            Address = "Thailand, BKK",
            Phone = "082-123-4569",
            Email = "pingkunga@debuggingsoft.com"
        },
        new SupplierDTO
        {
            Id = 2,
            FirstName = "Faii",
            LastName = "Foster",
            Address = "Thailand, Chiangrai",
            Phone = "082-123-4561",
            Email = "abc@Foster.com"
        },
        new SupplierDTO
        {
            Id = 3,
            FirstName = "Steve",
            LastName = "Kook",
            Address = "HK, Kowloon",
            Phone = "9999999999",
            Email = "xyz@abc.com"
        }
    };

    public async Task <IEnumerable <SupplierDTO>> GetSuppliers()
    {
        return await 
        Task.FromResult(suppliers);
    }

    public async Task <SupplierDTO> GetSupplier(int Id)
    {
        return await Task.FromResult(suppliers.FirstOrDefault(x => x.Id == Id));
    }
  }
  • ต่อมาเพิ่มในส่วนชั้น Service ครับ ล้อกันครับ โดยมีตัว ISupplierService / SupplierService Logic การทำงานตามนี้เลยครับ
namespace GraphQLAPI.Services;

using GraphQLAPI.Infra.Models;
public interface ISupplierService
{
    Task<IEnumerable<SupplierDTO>> GetSuppliers();
    Task<SupplierDTO> GetSupplier(int id);
    // Task<SupplierDTO> AddSupplier(Supplier supplier);
    // Task<SupplierDTO> UpdateSupplier(Supplier supplier);
    // Task<SupplierDTO> DeleteSupplier(int id);
}
namespace GraphQLAPI.Services;

using GraphQLAPI.Infra.Models;
using GraphQLAPI.Infra.Repositories;
public class SupplierService : ISupplierService
{
    private readonly ISupplierRepository _supplierRepository;

    public SupplierService(ISupplierRepository supplierRepository)
    {
        _supplierRepository = supplierRepository;
    }

    public async Task<IEnumerable<SupplierDTO>> GetSuppliers()
    {
        return await _supplierRepository.GetSuppliers();
    }

    public async Task<SupplierDTO> GetSupplier(int id)
    {
            return await _supplierRepository.GetSupplier(id);
    }
}
  • Register Service / Repository ให้ตัว NET8 DI จัดการต่อ ใน program.cs ให้เพิ่ม Code ลงไป ดังนี้
//Repiository
builder.Services.AddScoped<ISupplierRepository, SupplierRepository>();

//Service
builder.Services.AddScoped<ISupplierService, SupplierService>();
  • เพิ่มส่วน Controller
using Microsoft.AspNetCore.Mvc;
using GraphQLAPI.Services;
using GraphQLAPI.Infra.Models;

namespace GraphQLAPI.Controllers;

[Route("api/[controller]")]
[ApiController]
public class SupplierController : ControllerBase
{
    private readonly ISupplierService _supplierService;

    public SupplierController(ISupplierService supplierService)
    {
        _supplierService = supplierService;
    }

    [HttpGet]
    public async Task<IEnumerable<SupplierDTO>> GetSuppliers()
    {
        return await _supplierService.GetSuppliers();
    }

    [HttpGet("{id}")]
    public async Task<SupplierDTO> GetSupplier(int id)
    {
        return await _supplierService.GetSupplier(id);
    }
}

ตอนนี้ API ของเราพร้อมใช้งานแล้วครับ

-เพิ่มในส่วนของ GraphQL

จาก Blog เดิม จดจาก Build GraphQL APIs (Go) พบว่าส่วนประกอบของ GraphQL มี Type / Field (method) / Argument(method parameter) / Resolver (Field Resolver)

  • ส่วนแรกมาที่ GraphQL > Type มา Mapping ระหว่าง Data Model กับ GraphQL Type
using GraphQLAPI.Infra.Model;
namespace GraphQLAPI.GraphQL.Type
{
    public class SupplierType : ObjectType<SupplierDTO>
    {
        protected override void Configure(IObjectTypeDescriptor <SupplierDTO> descriptor)
        {
            descriptor.Field(a => a.Id).Type<IdType>();
            descriptor.Field(a => a.FirstName).Type<StringType>();
            descriptor.Field(a => a.LastName).Type<StringType>();
            descriptor.Field(a => a.Address).Type<StringType>();
            descriptor.Field(a => a.Phone).Type<StringType>();
        }
    }
}
  • ต่อ Map Field (method) / Argument(method parameter) และ Resolver (Field Resolver) โดยที่ Folder GraphQL > Query
using GraphQLAPI.Infra.Models;
using GraphQLAPI.Infra.Repositories;
using GraphQLAPI.Services;

namespace GraphQLAPI.GraphQL.Query
{
    public class SupplierGraphQLQuery
    {
        public async Task<IEnumerable<SupplierDTO>> GetAllSuppliers([Service]ISupplierService supplierService)
        {
            IEnumerable<SupplierDTO> suppliers = await supplierService.GetSuppliers();
            return suppliers;
        }

        public async Task<SupplierDTO> GetSupplierById([Service]ISupplierService supplierService, int id)
        {
            SupplierDTO supplier = await supplierService.GetSupplier(id);
            return supplier;
        }
    }
}
  • ต่อเพิ่มให้ GraphQL สร้าง Schema ออกมา
builder.Services.AddGraphQLServer()
                .AddType<SupplierType>()
                .AddQueryType<SupplierGraphQLQuery>();
  • และเปิด Endpoints ให้เข้่าไปเล่นในส่วนของ Playground
app.UsePlayground(new PlaygroundOptions
{
    QueryPath = "/graphql",
    Path = "/playground"
});
app.MapGraphQL(); //for frontend

Test

  • ลอง REST API แบบเดิมๆ
@GraphQLAPI_HostAddress = http://localhost:5126

## Rest API ##
### Get Supplier By id ###
GET {{GraphQLAPI_HostAddress}}/api/supplier

### Get all Supplier ###
GET {{GraphQLAPI_HostAddress}}/api/supplier/2
  • GraphQL API (Playground) <base_url>/playground
  • GraphQL API (.http) - เห็นว่าต้องส่งเป็น Post ทุกรอบนะ และต้องกำหนด X-REQUEST-TYPE: GraphQL โดยที่มี body ตามข้อกำหนดของ GraphQL
@GraphQLAPI_HostAddress = http://localhost:5126

### Get all Supplier ###
POST {{GraphQLAPI_HostAddress}}/graphQL
Content-Type: application/json
X-REQUEST-TYPE: GraphQL

query {
    allSuppliers{
        id
        firstName
        lastName
    }
}

### Get Supplier By id ###
POST {{GraphQLAPI_HostAddress}}/graphQL
Content-Type: application/json
X-REQUEST-TYPE: GraphQL

query {
  supplierById(id:2){
    firstName
    address
    phone
  }
}

เห็นไหมครับ จริงๆ Graph API ใช่ร่วมกับ REST API เดิมได้นะ

Code เต็มๆอยู่นี้ครับ : pingkunga/net8_graphql_HotChocolate_sample (github.com)

สำหรับ Blog หน้าจะไปในส่วน CREATE / UPDATE / DELETE ครับ


Discover more from naiwaen@DebuggingSoft

Subscribe to get the latest posts sent to your email.