gographqlgraphql-go

Golang GraphQL always returns one variable as null


GraphQL query field post and list return all data except created_at. It always null. I check GetPostBySlug and GetPostList variables and it contain data from Postgres. Where is my mistake?

model.go

package post

type Post struct {
    Slug      string `db:"slug"`
    Title     string `db:"title"`
    Content   string `db:"content"`
    Author    string `db:"author"`
    Category  string `db:"category"`
    CreatedAt string `db:"created_at"`
    Published bool   `db:"published"`
}

resolver.go

package post

import (
    "context"

    "github.com/graphql-go/graphql"
)

var postType = graphql.NewObject(
    graphql.ObjectConfig{
        Name: "Post",
        Fields: graphql.Fields{
            "slug": &graphql.Field{
                Type: graphql.String,
            },
            "title": &graphql.Field{
                Type: graphql.String,
            },
            "content": &graphql.Field{
                Type: graphql.String,
            },
            "category": &graphql.Field{
                Type: graphql.String,
            },
            "author": &graphql.Field{
                Type: graphql.String,
            },
            "created_at": &graphql.Field{
                Type: graphql.String,
            },
            "published": &graphql.Field{
                Type: graphql.Boolean,
            },
        },
    },
)

var queryType = graphql.NewObject(
    graphql.ObjectConfig{
        Name: "Query",
        Fields: graphql.Fields{
            "post": &graphql.Field{
                Type:        postType,
                Description: "Get post by slug",
                Args: graphql.FieldConfigArgument{
                    "slug": &graphql.ArgumentConfig{
                        Type: graphql.String,
                    },
                },
                Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                    var result interface{}

                    slug, _ := p.Args["slug"].(string)
                    result = GetPostBySlug(context.Background(), slug)

                    return result, nil
                },
            },
            "list": &graphql.Field{
                Type:        graphql.NewList(postType),
                Description: "Get post list",
                Args: graphql.FieldConfigArgument{
                    "limit": &graphql.ArgumentConfig{
                        Type: graphql.Int,
                    },
                },
                Resolve: func(params graphql.ResolveParams) (interface{}, error) {
                    var result interface{}

                    limit, _ := params.Args["limit"].(int)
                    result = GetPostList(context.Background(), limit)

                    return result, nil
                },
            },
        },
    })

var mutationType = graphql.NewObject(graphql.ObjectConfig{
    Name: "Mutation",
    Fields: graphql.Fields{
        "create": &graphql.Field{
            Type:        postType,
            Description: "Create new post",
            Args: graphql.FieldConfigArgument{
                "slug": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "title": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "content": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "category": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "author": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "published": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.Boolean),
                },
            },
            Resolve: func(params graphql.ResolveParams) (interface{}, error) {
                post := Post{
                    Slug:      params.Args["slug"].(string),
                    Title:     params.Args["title"].(string),
                    Content:   params.Args["content"].(string),
                    Category:  params.Args["category"].(string),
                    Author:    params.Args["author"].(string),
                    Published: params.Args["published"].(bool),
                }
                if err := InsertPost(context.Background(), post); err != nil {
                    return nil, err
                }
                return nil, nil
            },
        },
        "update": &graphql.Field{
            Type:        postType,
            Description: "Update post",
            Args: graphql.FieldConfigArgument{
                "slug": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "title": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "content": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "category": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "author": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
                "published": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.Boolean),
                },
            },
            Resolve: func(params graphql.ResolveParams) (interface{}, error) {
                post := Post{}

                if slug, slugOk := params.Args["slug"].(string); slugOk {
                    post.Slug = slug
                }
                if title, titleOk := params.Args["title"].(string); titleOk {
                    post.Title = title
                }
                if content, contentOk := params.Args["content"].(string); contentOk {
                    post.Content = content
                }

                if category, categoryOk := params.Args["category"].(string); categoryOk {
                    post.Category = category
                }

                if published, publishedOk := params.Args["published"].(bool); publishedOk {
                    post.Published = published
                }

                if old_slug, old_slugOk := params.Args["old-slug"].(string); old_slugOk {
                    if err := UpdatePost(context.Background(), post, old_slug); err != nil {
                        return nil, err
                    }
                }

                return nil, nil
            },
        },
        "delete": &graphql.Field{
            Type:        postType,
            Description: "Delete post by slug",
            Args: graphql.FieldConfigArgument{
                "slug": &graphql.ArgumentConfig{
                    Type: graphql.NewNonNull(graphql.String),
                },
            },
            Resolve: func(params graphql.ResolveParams) (interface{}, error) {
                slug, _ := params.Args["slug"].(string)
                if err := DeletePost(context.Background(), slug); err != nil {
                    return nil, err
                }
                return nil, nil
            },
        },
    },
})

var Schema, _ = graphql.NewSchema(
    graphql.SchemaConfig{
        Query:    queryType,
        Mutation: mutationType,
    },
)

repository.go

package post

import (
    "context"
    "errors"
    "fmt"
    "log"
    "os"
    "time"

    "github.com/jackc/pgx/v4"
    "github.com/joho/godotenv"
)

var (
    db      *pgx.Conn
    Info    *log.Logger
    Warning *log.Logger
    Error   *log.Logger
)

func init() {
    err := godotenv.Load("environment/.env")
    if err != nil {
        Error.Fatalf("Can't read \".env\" file: %s\n", err.Error())
    }

    Info = log.New(os.Stdout, "INFO: ", log.Ldate|log.Ltime|log.Lshortfile)
    Warning = log.New(os.Stdout, "WARNING: ", log.Ldate|log.Ltime|log.Lshortfile)
    Error = log.New(os.Stderr, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)

    db, err = pgx.Connect(
        context.Background(),
        fmt.Sprintf(
            "postgres://%s:%s@%s:%s/%s",
            os.Getenv("POSTGRES_USER"),
            os.Getenv("POSTGRES_PASSWORD"),
            os.Getenv("POSTGRES_HOST"),
            os.Getenv("POSTGRES_PORT"),
            os.Getenv("POSTGRES_DB"),
        ),
    )
    if err != nil {
        Error.Fatalf("Can't connect to database: %s\n", err.Error())
    }
}

func GetPostBySlug(ctx context.Context, slug string) (result interface{}) {
    row, err := db.Query(ctx, "SELECT slug, title, content, author, category, created_at, published FROM posts WHERE slug=$1 LIMIT 1", slug)
    if err != nil {
        Warning.Printf("GetPostBySlug query: %v\n", err.Error())
        return err
    }

    var post Post

    for row.Next() {
        var dateTime time.Time

        err = row.Scan(&post.Slug, &post.Title, &post.Content, &post.Author, &post.Category, &dateTime, &post.Published)
        if err != nil {
            Warning.Printf("Row scan: %v\n", err.Error())
            return nil
        }

        post.CreatedAt = dateTime.Format("02.01.2006")
    }

    return post
}

func GetPostList(ctx context.Context, limit int) (result interface{}) {
    var post Post
    var posts []Post

    var dateTime time.Time

    rows, err := db.Query(ctx, "SELECT slug, title, content, author, category, created_at, published FROM posts LIMIT $1", limit)
    if err != nil {
        Warning.Printf("GetPostList query: %v\n", err.Error())
        return err
    }

    for rows.Next() {
        err = rows.Scan(&post.Slug, &post.Title, &post.Content, &post.Author, &post.Category, &dateTime, &post.Published)
        if err != nil {
            Warning.Printf("Row scan: %v\n", err.Error())
            return nil
        }

        post.CreatedAt = dateTime.Format("02.01.2006")

        posts = append(posts, post)
    }

    return posts
}

func InsertPost(ctx context.Context, post Post) error {
    if SlugExists(ctx, post.Slug) {
        Warning.Printf("Slug \"%s\" already exists!\n", post.Slug)
        return errors.New("slug already exists")
    }

    _, err := db.Exec(
        ctx,
        "INSERT INTO posts (slug, title, content, author, category, created_at, published) VALUES ($1, $2, $3, $4, $5, CURRENT_TIMESTAMP, $6)",
        post.Slug, post.Title, post.Content, post.Author, post.Category, post.Published,
    )
    if err != nil {
        Warning.Printf("InsertPost exec query: %v\n", err.Error())
        return err
    }

    return nil
}

func SlugExists(ctx context.Context, slug string) bool {
    row, err := db.Query(ctx, "SELECT slug FROM posts WHERE slug=$1 LIMIT 1", slug)
    if err != nil {
        Error.Fatalf("SlugExists query: %v\n", err.Error())
    }

    var slug_exists string

    for row.Next() {
        err = row.Scan(&slug_exists)
        if err != nil {
            Error.Fatalf("Row scan: %v\n", err.Error())
        }
    }

    return slug_exists != ""
}

func UpdatePost(ctx context.Context, post Post, old_slug string) error {
    _, err := db.Exec(
        ctx,
        "UPDATE posts SET slug=$1, title=$2, content=$3, author=$4, category=$5, published=$6 WHERE slug=$7",
        post.Slug, post.Title, post.Content, post.Author, post.Category, post.Published, old_slug,
    )
    if err != nil {
        Warning.Printf("UpdatePost exec query: %v\n", err.Error())
        return err
    }

    return nil
}

func DeletePost(ctx context.Context, slug string) error {
    _, err := db.Exec(ctx, "DELETE FROM posts WHERE slug=$1", slug)
    if err != nil {
        Warning.Printf("DeletePost exec query: %v\n", err.Error())
        return err
    }

    return nil
}

query post result

query list result


Solution

  • To sum up what we found in the comments

    Rename GraphQL object created_at to createdAt