Create an API server (GraphQL)


Other than REST API, there is also an option to create an API server. For this article, I will use gqlgen library to initialize a GraphQL server.

First, create a Go module and initialize the project with gqlgen

mkdir graphql-api
cd graphql-api
go mod init gographql
go get github.com/99designs/gqlgen
go run github.com/99designs/gqlgen init

Then you will see gqlgen has generated many files for you. Then let's edit the graph/schema.graphqls file

type User {
  id: ID!
  name: String!
  age: Int!
}

type UserUpdated {
  user: User!
  info: String!
}

type Query {
  users: [User!]!
  user(id: ID!): User!
}

input CreateUserInput {
  name: String!
  age: Int!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  deleteUser(id: ID!): User!
}

type Subscription {
  userUpdated: UserUpdated!
}

Then regenerate the graphql Go files

go get github.com/99designs/gqlgen
go run github.com/99designs/gqlgen generate

In the graph/schema.resolvers.go file, you can delete TODO handlers that are in the bottom of the file.

Next, create store/store.go file to build a data structure to save user information; then insert this code

package store

import (
	"errors"
	"gographql/graph/model"
	"strconv"

	"github.com/google/uuid"
)

type UserStore struct {
	users []*model.User
}

func NewUserStore() *UserStore {
	return &UserStore{}
}

func (s *UserStore) Save(name string, age int) *model.User {
	id := uuid.New().ID()
	user := &model.User{
		ID:   strconv.FormatUint(uint64(id), 10),
		Name: name,
		Age:  age,
	}
	s.users = append(s.users, user)
	return user
}

func (s *UserStore) Delete(id string) (*model.User, error) {
	pos := -1
	for i, v := range s.users {
		if v.ID == id {
			pos = i
			break
		}
	}
	if pos == -1 {
		return nil, errors.New("user id not found")
	}
	user := s.users[pos]
	s.users = append(s.users[:pos], s.users[pos+1:]...)
	return user, nil
}

func (s *UserStore) FindByID(id string) (*model.User, error) {
	for _, v := range s.users {
		if v.ID == id {
			return v, nil
		}
	}
	return nil, errors.New("user id not found")
}

func (s *UserStore) GetAllUsers() []*model.User {
	return s.users
}

Then register the store in the graph/resolver.go file

type Resolver struct {
	mu              sync.Mutex
	UserSubscribers map[string]chan *model.UserUpdated
	UserStore       *store.UserStore
}

Also change the srv variable in main.go file

srv := handler.NewDefaultServer(
		generated.NewExecutableSchema(
			generated.Config{
				Resolvers: &graph.Resolver{
					UserSubscribers: make(map[string]chan *model.UserUpdated),
					UserStore:       store.NewUserStore(),
				},
			},
		),
	)
srv.AddTransport(transport.Websocket{
  KeepAlivePingInterval: time.Second * 10,
  Upgrader: websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
      return true
    },
  },
})

Now you can implement GraphQL handler function in the graph/schema.resolver.go file

func (r *mutationResolver) CreateUser(ctx context.Context, input model.CreateUserInput) (*model.User, error) {
	user := r.UserStore.Save(input.Name, input.Age)
	r.mu.Lock()
	for _, v := range r.UserSubscribers {
		v <- &model.UserUpdated{
			Info: "created",
			User: user,
		}
	}
	r.mu.Unlock()
	return user, nil
}

func (r *mutationResolver) DeleteUser(ctx context.Context, id string) (*model.User, error) {
	user, err := r.UserStore.Delete(id)
	if err != nil {
		return nil, err
	}
	r.mu.Lock()
	for _, v := range r.UserSubscribers {
		v <- &model.UserUpdated{
			Info: "deleted",
			User: user,
		}
	}
	r.mu.Unlock()
	return user, nil
}

func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
	return r.UserStore.GetAllUsers(), nil
}

func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
	return r.UserStore.FindByID(id)
}

func (r *subscriptionResolver) UserUpdated(ctx context.Context) (<-chan *model.UserUpdated, error) {
	subscriberID := uuid.New().String()
	ch := make(chan *model.UserUpdated, 1)
	r.mu.Lock()
	r.UserSubscribers[subscriberID] = ch
	r.mu.Unlock()

	go func() {
		<-ctx.Done()
		r.mu.Lock()
		delete(r.UserSubscribers, subscriberID)
		r.mu.Unlock()
	}()

	return ch, nil
}

Finally, you can start the GraphQL API server

go run .

visit the GraphQL playground at localhost:8080 and test all queries we have implemented

mutation {
  createUser(input: {name: "albert", age: 13}) {
    id
    name
  }
}
query {
  users {
    id
    name
    age
  }
}
query {
  user(id: "xxxxxxxxxxx") {
    name
    age
  }
}
mutation {
  deleteUser(id: "xxxxxxxxxxx") {
    name
    age
  }
}

For the subscription function, open another tab for the playground and do this query

subscription {
  userUpdated {
    info
    user {
      id
      name
      age
    }
  }
}

Try to create or delete a user from the other tab to see what will happen.

To use this GraphQL with other frontend application, by default you will need to connect to localhost:8080/query. You might use Apollo library for JS to make API requests from your website application. Thank you for reading...

sources