That String Array

October 11, 2019
golang gqlgen graphql sql cockroachdb

If you are using gqlgen to generate your GraphQL endpoint and sqlx or some other kind of ORM for your database, you can benefit from using one type or struct in both directions. You create one type User struct for GraphQL and it maps to your db schema. Now if this user contains more complex types, things become interesting…



We have a struct with a string slice type:

type User struct {
    Friends []string `db:"friends"`

This works quite well with gqlgen as it translates straight forward to a [String] in GraphQL (quick warning here: If the struct doesn’t match the graphql schema, gqlgen just doesn’t generate the code leaving you with a partially implemented interface of your endpoint).

Now let’s have a look at the database side of things. The schema representing our users will look like this:

    friends STRING[]

Notice the friends column having the type STRING[] which is equivalent to STRING ARRAY.

If if we now select from that table and want to scan into our User struct, we are greeted with:

Error: sql: Scan error on column index 1, name “friends”: unsupported Scan, storing driver.Value type []uint8 into type *[]string

Apparently the driver returns a byte array.


This seems like a problem someone else had before, so a quick search suggest pq.StringArray as the matching type. This leaves us with a new struct definition:

type User struct {
    Friends pq.StringArray `db:"friends"`

This solves the issue with scanning the column into the struct. Reason for this is the .Value and .Scan methods on the StringArray type instructing sql how to scan the data from the database. More details on this can be found in the sqlx documentation.

Now when regenerating the GraphQL endpoint, our User suddenly disappears. From our gqlgen configuration we know that we provide the model for the User:


so when we generate the GraphQL endpoint, gqlgen fails as the pq.StringArray doesn’t match the [String] in the schema definition. In order to fix this, we have to update our GraphQL schema:

type User {
  friends: StringArray

StringArray is a custom scalar which we can define as scalar StringArray in the schema. StringArray is not defined yet so we point gqlgen to the correct place via the configuration:


Where qgl is my package containing everything gqlgen. Types are sorted now and we get back our User in the GraphQL schema. The next problem is that gqlgen doesn’t know how to serialize from or to the StringArray type. This problem can be solved by providing an external marshaler which is very well described in the gqlgen documentation.

func MarshalStringArray(a pq.StringArray) graphql.Marshaler {
	return graphql.WriterFunc(func(w io.Writer) {
		data, _ := json.Marshal(a)
		io.WriteString(w, string(data))

func UnmarshalStringArray(v interface{}) (pq.StringArray, error) {
	a, ok := v.(pq.StringArray)
	if !ok {
		return nil, errors.New("failed to cast to pq.StringArray")
	return a, nil

The data, _ := json.Marshal(a) line is for convenience and asks for a more sophisticated solution.

Now all those steps together will provide you with a single model which can be used by gqlgen to generate the GraphQL endpoint and by sqlx to write and scan.