Error "The ID `1` has an invalid format" when querying HotChocolate

1.9k views Asked by At

Tried to make own project, looking to ChilliCream/graphql-workshop as example.

There is a part, where id parameter of a query marked with IDAttribute.

Description of ID type says following:

The ID scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as "4") or integer (such as 4) input value will be accepted as an ID.

My C# query source looks like

[ExtendObjectType(Name = GraphqlQueryNames.Query)]
public class EmployeeQuery
{
    public async Task<Employee> GetEmployeeByIdAsync(
        [ID] int id,
        [Service] IEmployeeRepository employeeRepository,
        CancellationToken token)
    {
        return await employeeRepository.GetEmployeeByIdAsync(id, token);
    }
}

And in playground:

# 1 passed as value of $id

query getEmployeeById($id: ID!) {
  employeeById(id: $id) {
    familyName
  }
}

Whether value is a string or a number, server throws same error "The ID `1` has an invalid format".

If we remove the [ID] attribute from C# and use it as 'Int!' in GraphQL query, it works fine.

What's wrong with ID and why it exists in example (AttendeeQueries.cs)? HotChocolate 10.5.3

2

There are 2 answers

3
Thomas On BEST ANSWER

First, the ID scalar type is part of GraphQL standard, and defined as:

In GraphQL the ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.

Relay is a GraphQL client JavaScript framework for React applications. Apollo GraphQL is another alternative.

HotChocolate has a few helpers to enable "Relay-style GraphQL API". These helpers will convert the id-field to a base64-encoded hash, serialized as a string. This is a good thing because:

  • it helps with caching, all entities has a unique id
  • hide actual id from users (?)

Even though you enable "Relay support" in HotChocolate, you don't have to use Relay, you can still use any GraphQL client (Apollo client is my favourite)

Now, if you just want to use the GraphQL ID scalar type, you can try this as I suggested first:

First remove ID-attribute from query:

[ExtendObjectType(Name = GraphqlQueryNames.Query)]
public class EmployeeQuery
{
    public async Task<Employee> GetEmployeeByIdAsync(
        int id,
        [Service] IEmployeeRepository employeeRepository,
        CancellationToken token)
    {
        return await employeeRepository.GetEmployeeByIdAsync(id, token);
    }
}

then specify ID scalar type like this:

public class EmployeeType : ObjectType<Employee>
{
    protected override void Configure(IObjectTypeDescriptor<Employee> descriptor)
    {
        descriptor.Field(r => r.Id).Type<IdType>;
    }
}

But if you want to enable "Relay support" in HotChocolate, follow Arsync's answer (I changed this to HotChocolate v11 which has slightly different syntax):

public class Startup
{
  public void ConfigureServices(IServiceCollection services) 
  {
    services.AddGraphQLServer().EnableRelaySupport();
  }
}

Update for Hot Chocolate v12: It's now possible to enable global identification, but without other relay related stuff:

public class Startup
{
  public void ConfigureServices(IServiceCollection services) 
  {
    services.AddGraphQLServer().AddGlobalObjectIdentification();
  }
}

Then in your type definition:

public class EmployeeType : ObjectType<Employee>
{
    protected override void Configure(IObjectTypeDescriptor<Employee> descriptor)
    {
        // Relay ID support. Retailer.Id will be a hash. the real id / int is available below when passed to DataLoader
        descriptor
            .ImplementsNode()
            .IdField(c => c.Id)
            .ResolveNode(((context, id) => context.DataLoader<EmployeeByIdDataLoader>().LoadAsync(id, context.RequestAborted)));
    }
}

Or even simpler with v12:

public class EmployeeType : ObjectType<Employee>
{
    protected override void Configure(IObjectTypeDescriptor<Employee> descriptor)
    {
        descriptor
            .Field(f => f.Id).ID(nameof(Employee));
    }
}

If you try to query for employees now you'll see that id is not an integer, but a hash. Something like "TGFuZ3VhZ2UKaTE=". This hash is generated by HotChocolate, see IdSerializer source. If you try to base64 decode this string:

$ echo "TGFuZ3VhZ2UKaTE=" | base64 -d
Employee
i1

The errormessage you received "The ID 1 has invalid format", is because it now expects a hashed string, and not an integer.

This query should work:

query getEmployeeById {
  employeeById(id: "TGFuZ3VhZ2UKaTE=") {
    familyName
  }
}
0
Arsync On

Found that IDAttribute is for Relay (as it located in HotChocolate.Types.Relay namespace). So need enable and configure Relay support (source):

ISchema schema = SchemaBuilder.New()
    .EnableRelaySupport()
    ...
    .Create();

And in ObjectType:

public class MyObjectType
    : ObjectType<MyObject>
{
    protected override void Configure(IObjectTypeDescriptor<MyObject> descriptor)
    {
        descriptor.AsNode()
            .IdField(t => t.Id)
            .NodeResolver((ctx, id) =>
                ctx.Service<IMyRepository>().GetMyObjectAsync(id));
        ...
    }
}

Seems the example project graphql-workshop needs more in-place explanation of purpose for these things. Can be found here.