gqlgen 및 Gorm을 사용하여 Go에서 GraphQL Api 빌드

소개



오늘의 튜토리얼에서는 스키마 우선 접근 방식이 있고 유형이 안전하며 여전히 codegen을 사용하여 데이터 유형과 리졸버를 생성하는 gqlgen 프레임워크를 사용하여 graphql API를 생성할 것입니다.

내가 gqlgen에 대해 가장 놀라운 점은 codegen이 구성 가능하고 매우 완전한 문서를 가지고 있으며 배우기 쉽고 기능면에서 상당히 완전하다는 것입니다.

전제 조건



계속 진행하기 전에 다음 기술에 대한 기본 지식이 있어야 합니다.
  • 그래프QL
  • ORM의

  • 시작하기



    첫 번째 단계는 프로젝트 폴더를 만드는 것입니다.

    mkdir haumea
    cd haumea
    go mod init haumea
    


    다음으로 gqlgen를 사용하여 프로젝트를 초기화하겠습니다.

    go run github.com/99designs/gqlgen init
    


    이제 프로젝트의 구조와 관련하여 몇 가지 변경을 할 수 있습니다. graph/ 폴더 내에서 다음 폴더와 파일을 삭제합니다.
  • model/ - 이 폴더에는 프로젝트의 모델/데이터 유형이 있습니다
  • .
  • resolver.goschema.resolvers.go - 이 파일은 스키마 해석기
  • 의 구현과 관련이 있습니다.
  • schema.graphqls - API/프로젝트 graphql 스키마

  • 파일과 폴더를 제거한 후 이제 gqlgen.yml 파일을 편집하여 다음과 같은 구성을 추가할 수 있습니다.

    # @/gqlgen.yml
    schema:
      - graph/typeDefs/*.gql
    
    exec:
      filename: graph/generated/generated.go
      package: generated
    
    model:
      filename: graph/customTypes/types_gen.go
      package: customTypes
    
    resolver:
      layout: follow-schema
      dir: graph/resolvers
      package: graph
      filename_template: "{name}.resolvers.go"
    
    autobind:
    
    models:
      ID:
        model:
          - github.com/99designs/gqlgen/graphql.Int
          - github.com/99designs/gqlgen/graphql.ID
          - github.com/99designs/gqlgen/graphql.Int64
          - github.com/99designs/gqlgen/graphql.Int32
      Int:
        model:
          - github.com/99designs/gqlgen/graphql.Int
          - github.com/99designs/gqlgen/graphql.Int64
          - github.com/99designs/gqlgen/graphql.Int32
    


    위의 구성에서 다음과 같이 변경했습니다.
  • 모든 스키마 파일은 typeDefs 폴더 안에 있으며 확장자는 .gql
  • 입니다.
  • 모델/데이터 유형은 customTypes/라는 파일의 types_gen.go 폴더에 저장됩니다.
  • 리졸버는 resolvers/ 폴더 안에 생성됩니다.
  • graphql 유형ID을 사용할 때 식별자(id)가 golang 데이터 유형Int에 매핑됩니다.

  • 이를 염두에 두고 이제 오늘 프로젝트의 스키마를 생성할 수 있습니다. 먼저 typeDefs/ 폴더를 생성하고 그 안에 todo.gql 파일을 생성해 보겠습니다.

    # @/graph/typeDefs/todo.gql
    type Todo {
      id: ID!
      text: String!
      done: Boolean!
    }
    
    input TodoInput {
      id: ID!
      text: String!
      done: Boolean!
    }
    
    type Mutation {
      createTodo(text: String!): Todo!
      updateTodo(input: TodoInput!): Todo!
      deleteTodo(todoId: ID!): Todo!
    }
    
    type Query {
      getTodos: [Todo!]!
      getTodo(todoId: ID!): Todo!
    }
    


    스키마가 생성되면 다음 단계는 쿼리 및 변형에 대한 데이터 유형 및 확인자를 생성하는 것입니다.

    go run github.com/99designs/gqlgen generate
    


    이제 이 모든 작업을 완료했으므로 데이터베이스에 대한 연결을 설정하는 다음 단계로 넘어갈 수 있습니다.

    go get -u gorm.io/gorm
    go get -u gorm.io/driver/sqlite
    


    그런 다음 common/ 파일의 데이터베이스 연결 구성을 사용하여 db.go라는 폴더를 만들 수 있습니다.

    // @/graph/common/db.go
    package common
    
    import (
        "haumea/graph/customTypes"
    
        "gorm.io/driver/sqlite"
        "gorm.io/gorm"
        "gorm.io/gorm/logger"
    )
    
    func InitDb() (*gorm.DB, error) {
        var err error
        db, err := gorm.Open(sqlite.Open("dev.db"), &gorm.Config{
            Logger: logger.Default.LogMode(logger.Silent),
        })
        if err != nil {
            return nil, err
        }
        db.AutoMigrate(&customTypes.Todo{})
        return db, nil
    }
    


    데이터베이스 연결이 구성되었으므로 이제 graphql api 컨텍스트를 생성할 수 있습니다. 여전히 common/ 폴더 안에 context.go 파일을 생성합니다(이 문서에서는 데이터베이스 연결만 전달하지만 컨텍스트를 쉽게 확장할 수 있음). :

    // @/graph/common/context.go
    package common
    
    import (
        "context"
        "net/http"
    
        "gorm.io/gorm"
    )
    
    type CustomContext struct {
        Database *gorm.DB
    }
    
    var customContextKey string = "CUSTOM_CONTEXT"
    
    func CreateContext(args *CustomContext, next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            customContext := &CustomContext{
                Database: args.Database,
            }
            requestWithCtx := r.WithContext(context.WithValue(r.Context(), customContextKey, customContext))
            next.ServeHTTP(w, requestWithCtx)
        })
    }
    
    func GetContext(ctx context.Context) *CustomContext {
        customContext, ok := ctx.Value(customContextKey).(*CustomContext)
        if !ok {
            return nil
        }
        return customContext
    }
    


    이제 API의 항목 파일로 이동하여 데이터베이스 연결을 시작하고 API 컨텍스트에서 데이터베이스를 전달해야 합니다. 이 방법:

    // @/server.go
    package main
    
    import (
        "haumea/common"
        "haumea/graph/generated"
        resolvers "haumea/graph/resolvers"
        "log"
        "net/http"
        "os"
    
        "github.com/99designs/gqlgen/graphql/handler"
        "github.com/99designs/gqlgen/graphql/playground"
    )
    
    const defaultPort = "4000"
    
    func main() {
        port := os.Getenv("PORT")
        if port == "" {
            port = defaultPort
        }
    
        db, err := common.InitDb()
        if err != nil {
            log.Fatal(err)
        }
    
        srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &resolvers.Resolver{}}))
    
        customCtx := &common.CustomContext{
            Database: db,
        }
    
        http.Handle("/", playground.Handler("GraphQL playground", "/query"))
        http.Handle("/query", common.CreateContext(customCtx, srv))
    
        log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
        log.Fatal(http.ListenAndServe(":"+port, nil))
    }
    


    이제 서버를 구성하고 데이터베이스 설정을 완료했으며 연결을 생성했으므로 리졸버 작업만 하면 됩니다.

    각 리졸버에서 생성한 컨텍스트에 액세스하려면 CRUD를 수행하기 위해 데이터베이스 인스턴스를 가져올 GetContext() 함수를 사용합니다.

    // @/graph/resolvers/todo.resolvers.go
    package graph
    
    import (
        "context"
        "haumea/common"
        "haumea/graph/customTypes"
        "haumea/graph/generated"
    )
    
    func (r *mutationResolver) CreateTodo(ctx context.Context, text string) (*customTypes.Todo, error) {
        context := common.GetContext(ctx)
        todo := &customTypes.Todo{
            Text: text,
            Done: false,
        }
        err := context.Database.Create(&todo).Error
        if err != nil {
            return nil, err
        }
        return todo, nil
    }
    
    func (r *mutationResolver) UpdateTodo(ctx context.Context, input customTypes.TodoInput) (*customTypes.Todo, error) {
        context := common.GetContext(ctx)
        todo := &customTypes.Todo{
            ID:   input.ID,
            Text: input.Text,
            Done: input.Done,
        }
        err := context.Database.Save(&todo).Error
        if err != nil {
            return nil, err
        }
        return todo, nil
    }
    
    func (r *mutationResolver) DeleteTodo(ctx context.Context, todoID int) (*customTypes.Todo, error) {
        context := common.GetContext(ctx)
        var todo *customTypes.Todo
        err := context.Database.Where("id = ?", todoID).Delete(&todo).Error
        if err != nil {
            return nil, err
        }
        return todo, nil
    }
    
    func (r *queryResolver) GetTodos(ctx context.Context) ([]*customTypes.Todo, error) {
        context := common.GetContext(ctx)
        var todos []*customTypes.Todo
        err := context.Database.Find(&todos).Error
        if err != nil {
            return nil, err
        }
        return todos, nil
    }
    
    func (r *queryResolver) GetTodo(ctx context.Context, todoID int) (*customTypes.Todo, error) {
        context := common.GetContext(ctx)
        var todo *customTypes.Todo
        err := context.Database.Where("id = ?", todoID).Find(&todo).Error
        if err != nil {
            return nil, err
        }
        return todo, nil
    }
    
    func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
    
    func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
    
    type mutationResolver struct{ *Resolver }
    type queryResolver struct{ *Resolver }
    


    서버를 실행하려면 다음 명령을 사용하십시오.

    go run server.go
    


    그리고 이 프로젝트의 각 쿼리와 변형을 테스트하려면 브라우저에서 새 탭을 열고 http://localhost:4000/ 를 방문하십시오.

    결론



    늘 그렇듯이 기사가 마음에 드셨기를 바라며 기존 프로젝트에 도움이 되었거나 단순히 사용해 보고 싶으셨기를 바랍니다.

    기사에서 잘못된 부분을 발견했다면 댓글로 알려주시면 수정하겠습니다. 마치기 전에 이 기사의 소스 코드에 액세스하려면 github 저장소에 대한 링크here를 남겨둡니다.

    좋은 웹페이지 즐겨찾기