GraphQL
GraphQL adalah query language untuk API yang memberikan client kemampuan untuk meminta data yang spesifik. Go memiliki beberapa package untuk implementasi GraphQL server.
Contoh Masalah
Bagaimana cara:
- Setup GraphQL server
- Define schema
- Implement resolvers
- Handle mutations
Penyelesaian
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"
"time"
"github.com/graphql-go/graphql"
)
// 1. Data types
type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"createdAt"`
Posts []Post `json:"posts"`
}
type Post struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
AuthorID string `json:"authorId"`
CreatedAt time.Time `json:"createdAt"`
Comments []Comment `json:"comments"`
}
type Comment struct {
ID string `json:"id"`
Content string `json:"content"`
PostID string `json:"postId"`
AuthorID string `json:"authorId"`
CreatedAt time.Time `json:"createdAt"`
}
// 2. In-memory database
type DB struct {
sync.RWMutex
users map[string]User
posts map[string]Post
comments map[string]Comment
}
func NewDB() *DB {
return &DB{
users: make(map[string]User),
posts: make(map[string]Post),
comments: make(map[string]Comment),
}
}
// 3. GraphQL types
var userType = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"name": &graphql.Field{
Type: graphql.String,
},
"email": &graphql.Field{
Type: graphql.String,
},
"createdAt": &graphql.Field{
Type: graphql.DateTime,
},
"posts": &graphql.Field{
Type: graphql.NewList(postType),
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
user := p.Source.(User)
var userPosts []Post
for _, post := range db.posts {
if post.AuthorID == user.ID {
userPosts = append(userPosts, post)
}
}
return userPosts, nil
},
},
},
},
)
var postType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Post",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"title": &graphql.Field{
Type: graphql.String,
},
"content": &graphql.Field{
Type: graphql.String,
},
"authorId": &graphql.Field{
Type: graphql.String,
},
"createdAt": &graphql.Field{
Type: graphql.DateTime,
},
"author": &graphql.Field{
Type: userType,
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
post := p.Source.(Post)
user, exists := db.users[post.AuthorID]
if !exists {
return nil, fmt.Errorf("author not found")
}
return user, nil
},
},
"comments": &graphql.Field{
Type: graphql.NewList(commentType),
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
post := p.Source.(Post)
var postComments []Comment
for _, comment := range db.comments {
if comment.PostID == post.ID {
postComments = append(postComments,
comment)
}
}
return postComments, nil
},
},
},
},
)
var commentType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Comment",
Fields: graphql.Fields{
"id": &graphql.Field{
Type: graphql.String,
},
"content": &graphql.Field{
Type: graphql.String,
},
"postId": &graphql.Field{
Type: graphql.String,
},
"authorId": &graphql.Field{
Type: graphql.String,
},
"createdAt": &graphql.Field{
Type: graphql.DateTime,
},
"author": &graphql.Field{
Type: userType,
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
comment := p.Source.(Comment)
user, exists := db.users[comment.AuthorID]
if !exists {
return nil, fmt.Errorf("author not found")
}
return user, nil
},
},
"post": &graphql.Field{
Type: postType,
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
comment := p.Source.(Comment)
post, exists := db.posts[comment.PostID]
if !exists {
return nil, fmt.Errorf("post not found")
}
return post, nil
},
},
},
},
)
// 4. Query type
var queryType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"user": &graphql.Field{
Type: userType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
id := p.Args["id"].(string)
user, exists := db.users[id]
if !exists {
return nil, fmt.Errorf("user not found")
}
return user, nil
},
},
"users": &graphql.Field{
Type: graphql.NewList(userType),
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
var users []User
for _, user := range db.users {
users = append(users, user)
}
return users, nil
},
},
"post": &graphql.Field{
Type: postType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
id := p.Args["id"].(string)
post, exists := db.posts[id]
if !exists {
return nil, fmt.Errorf("post not found")
}
return post, nil
},
},
},
},
)
// 5. Mutation type
var mutationType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Mutation",
Fields: graphql.Fields{
"createUser": &graphql.Field{
Type: userType,
Args: graphql.FieldConfigArgument{
"name": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"email": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
name := p.Args["name"].(string)
email := p.Args["email"].(string)
user := User{
ID: fmt.Sprintf("user_%d",
time.Now().UnixNano()),
Name: name,
Email: email,
CreatedAt: time.Now(),
}
db.Lock()
db.users[user.ID] = user
db.Unlock()
return user, nil
},
},
"createPost": &graphql.Field{
Type: postType,
Args: graphql.FieldConfigArgument{
"title": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"content": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"authorId": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
},
Resolve: func(p graphql.ResolveParams) (interface{},
error) {
db := p.Context.Value("db").(*DB)
title := p.Args["title"].(string)
content := p.Args["content"].(string)
authorID := p.Args["authorId"].(string)
if _, exists := db.users[authorID]; !exists {
return nil, fmt.Errorf("author not found")
}
post := Post{
ID: fmt.Sprintf("post_%d",
time.Now().UnixNano()),
Title: title,
Content: content,
AuthorID: authorID,
CreatedAt: time.Now(),
}
db.Lock()
db.posts[post.ID] = post
db.Unlock()
return post, nil
},
},
},
},
)
// 6. Schema
var schema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: queryType,
Mutation: mutationType,
},
)
// 7. HTTP handler
func graphqlHandler(db *DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var params struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
}
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
ctx := context.WithValue(r.Context(), "db", db)
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: params.Query,
Context: ctx,
OperationName: params.OperationName,
Variables: params.Variables,
})
if len(result.Errors) > 0 {
w.WriteHeader(http.StatusBadRequest)
}
json.NewEncoder(w).Encode(result)
}
}
func main() {
// Create database
db := NewDB()
// Add sample data
user := User{
ID: "user_1",
Name: "John Doe",
Email: "john@example.com",
CreatedAt: time.Now(),
}
db.users[user.ID] = user
post := Post{
ID: "post_1",
Title: "Hello GraphQL",
Content: "This is my first post",
AuthorID: user.ID,
CreatedAt: time.Now(),
}
db.posts[post.ID] = post
// Setup HTTP server
http.HandleFunc("/graphql", graphqlHandler(db))
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Example Queries
- Get user with posts:
{
user(id: "user_1") {
id
name
email
posts {
id
title
content
}
}
}
- Create user:
mutation {
createUser(name: "Jane Doe", email: "jane@example.com") {
id
name
email
createdAt
}
}
- Create post:
mutation {
createPost(
title: "My Post"
content: "Post content"
authorId: "user_1"
) {
id
title
content
author {
name
}
}
}
Penjelasan Kode
GraphQL Components
- Schema
- Types
- Resolvers
- Context
Features
- Queries
- Mutations
- Nested resolvers
- Arguments
Best Practices
- Error handling
- Context usage
- Type definitions
Setup
- Install dependencies:
go get github.com/graphql-go/graphql
- Run server:
go run main.go
- Test queries:
curl -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ user(id: \"user_1\") { name email } }"}'
Output
2024/01/21 11:57:22 Server starting on :8080
Query response:
{
"data": {
"user": {
"name": "John Doe",
"email": "john@example.com"
}
}
}
Mutation response:
{
"data": {
"createUser": {
"id": "user_1234567890",
"name": "Jane Doe",
"email": "jane@example.com",
"createdAt": "2024-01-21T11:57:23Z"
}
}
}
Tips
- Define clear types
- Use context
- Handle errors
- Implement validation
- Monitor performance