20 Managing Queries with Parameters in graphql-go
title: 20 Managing Queries with Parameters in graphql-go
date: 2024-06-26
author: Faris Azhar
tags: [Go, GraphQL, Back-End, API, Tutorial]
GraphQL offers remarkable flexibility for building modern APIs—including in how you manage queries with parameters. For backend engineers working in Go, the graphql-go
package is one of the go-to choices. However, managing parameters in GraphQL queries can sometimes become a stumbling block of its own, whether in terms of schema design or resolver implementation.
In this article, I’ll thoroughly cover how to manage queries with parameters using graphql-go, complete with 20 best practices and essential tricks. We’ll discuss everything conceptually, through code practice, all the way to real-world examples involving filtering, pagination, validation, and error handling.
1. Defining Parameters in the Schema
The first step is understanding where parameters belong in a GraphQL schema. Parameters are attached as arguments on a field:
1userType := graphql.NewObject(graphql.ObjectConfig{
2 Name: "User",
3 Fields: graphql.Fields{
4 "id": &graphql.Field{Type: graphql.String},
5 "name": &graphql.Field{Type: graphql.String},
6 },
7})
8
9queryType := graphql.NewObject(graphql.ObjectConfig{
10 Name: "Query",
11 Fields: graphql.Fields{
12 "user": &graphql.Field{
13 Type: userType,
14 Args: graphql.FieldConfigArgument{
15 "id": &graphql.ArgumentConfig{
16 Type: graphql.NewNonNull(graphql.String),
17 },
18 },
19 Resolve: userResolver,
20 },
21 },
22})1{
2 user(id: "1") {
3 name
4 }
5}2. Using a Default Value on an Argument
An argument can be given a default value, so the client doesn’t always have to provide it:
1"status": &graphql.ArgumentConfig{
2 Type: graphql.String,
3 DefaultValue: "active",
4},3. Defining Complex Arguments (Input Object)
For more complex parameters, use an Object Input Type:
1userFilterInput := graphql.NewInputObject(graphql.InputObjectConfig{
2 Name: "UserFilter",
3 Fields: graphql.InputObjectConfigFieldMap{
4 "name": &graphql.InputObjectFieldConfig{Type: graphql.String},
5 "age": &graphql.InputObjectFieldConfig{Type: graphql.Int},
6 },
7})
8
9"users": &graphql.Field{
10 Type: graphql.NewList(userType),
11 Args: graphql.FieldConfigArgument{
12 "filter": &graphql.ArgumentConfig{Type: userFilterInput},
13 },
14 Resolve: usersResolver,
15}4. Reading Parameters in the Resolver
Arguments are obtained through params.Args inside the resolver:
1func userResolver(p graphql.ResolveParams) (interface{}, error) {
2 id, ok := p.Args["id"].(string)
3 if !ok {
4 return nil, errors.New("ID is required")
5 }
6 // Query the database
7}5. Validating Arguments
Always validate before passing a parameter on to the next step in the process.
1if len(id) != 36 {
2 return nil, errors.New("Invalid ID format")
3}6. Optional Parameters and Nullables
A common mistake happens when an optional parameter’s value isn’t checked (whether or not it exists in params.Args).
1var name string
2if n, exists := p.Args["name"]; exists {
3 name = n.(string)
4}7. Multi-Field Filtering
Allow filtering with an array/list on an argument.
1"tags": &graphql.ArgumentConfig{
2 Type: graphql.NewList(graphql.String),
3},8. Implementing Pagination
Pagination is very common. Make use of the limit and offset parameters:
1"users": &graphql.Field{
2 Type: graphql.NewList(userType),
3 Args: graphql.FieldConfigArgument{
4 "limit": &graphql.ArgumentConfig{Type: graphql.Int, DefaultValue: 10},
5 "offset": &graphql.ArgumentConfig{Type: graphql.Int, DefaultValue: 0},
6 },
7 Resolve: usersResolver,
8}9. Enum Parameters
Define an Enum type so the caller can only choose valid values.
1statusEnum := graphql.NewEnum(graphql.EnumConfig{
2 Name: "Status",
3 Values: graphql.EnumValueConfigMap{
4 "ACTIVE": &graphql.EnumValueConfig{Value: "active"},
5 "INACTIVE": &graphql.EnumValueConfig{Value: "inactive"},
6 },
7})10. Nested Parameters
Create an input type within an input type for nested queries, for example a filter inside a filter.
11. Array Parameters
An argument of the list type, for example multiple IDs.
1"ids": &graphql.ArgumentConfig{
2 Type: graphql.NewList(graphql.String),
3}12. Input Validation in the Resolver
In addition to the schema, add validation in the resolver. For example, checking uniqueness, foreign key relationships, and so on.
13. Simulating Queries with Parameters
Let’s compare queries with and without parameters:
| Query | Response |
|---|---|
users(limit: 2) | [{"id":"1"}, {"id":"2"}] |
user(id: "xyz") | {"id": "xyz", "name": "Dewi"} |
users(filter: {...}) | [{"id":"a"}, {"id":"b"}] |
14. Error Handling
Always return a “clear” error in the resolver when validation fails.
15. Query Variables
Use query variables instead of hard-coding parameters in the client:
1query getUser($id: String!) {
2 user(id: $id) {
3 name
4 }
5}
1query getUser($id: String!) {
2 user(id: $id) {
3 name
4 }
5}16. Documenting Schema Parameters
Make use of descriptions in the schema, so frontend developers can easily understand which arguments are available.
17. Dynamic Query Parameters
When a parameter can be one of many fields (for example, filtering by email or username):
1Args: graphql.FieldConfigArgument{
2 "email": &graphql.ArgumentConfig{Type: graphql.String},
3 "username": &graphql.ArgumentConfig{Type: graphql.String},
4}In the resolver, check which argument is filled in.
18. Process Flow Diagram for Parameterized Queries
For a clearer picture, here is the resolver flow in a query with parameters in graphql-go:
graph TD
A[Klien Mengirim Query dgn Parameter] --> B[GraphQL Handler Receive]
B --> C[Schema Validate Param]
C --> D[Resolver Ambil Params]
D --> E[Validasi & Filtering di Go]
E --> F[Query ke DB]
F --> G[Return Response]
19. Limiting Response Fields with Parameters
You can also limit which fields are queried using a parameter, similar to projection in SQL/MongoDB.
20. Separation of Concerns
Separate your GraphQL handler logic, parameter validation, data access, and error handling within your Go code.
Conclusion
Managing queries with parameters in graphql-go is fundamental if you want to build robust, scalable, and maintainable GraphQL APIs in Go. To wrap up, here is a summary table of best practices:
| No | Best Practice | Implementation Example |
|---|---|---|
| 1 | Parameters in the Schema | Args: FieldConfigArgument{...} |
| 4 | Params in the Resolver | p.Args["param_name"] |
| 8 | Pagination | limit, offset args & validate in Go |
| 13 | Simulating Queries | Check different results based on arguments |
| 20 | Separation of Concerns | Separate handler, validation, data, error |
Start optimizing your implementation, even for queries that may look trivial. Security, efficiency, and ease of debugging are worthwhile investments for your next Go project!
Have other tips that weren’t covered here? Drop them in the comments—let’s discuss! 🚀