Why Has GraphQL Struggled to Become Popular? Under-Design or Over-Design?

Published: (December 13, 2025 at 09:58 AM EST)
4 min read
Source: Dev.to

Source: Dev.to

I. Simplifying GraphQL Service Development

If you ask Zhipu Qingyan AI to write a GraphQL example using the getBookById function, it will generate the following code:

@Configuration
public class GraphQLConfig {

    @Bean
    public GraphQLSchema graphQLSchema() {
        String schema = "type Book { id: ID!, title: String, author: String }" +
                        "type Query { getBookById(id: ID!): Book }";
        SchemaParser schemaParser = new SchemaParser();
        TypeDefinitionRegistry typeDefinitionRegistry = schemaParser.parse(schema);

        RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
                .type("Query", builder ->
                   builder.dataFetcher("getBookById", environment -> {
                    String bookId = environment.getArgument("id");
                    // Add logic to fetch the book here; this is just an example
                    return Book.builder().id(bookId)
                        .title("Sample Book Name").author("Sample Author").build();
                }))
                .build();

        SchemaGenerator schemaGenerator = new SchemaGenerator();
        return schemaGenerator.makeExecutableSchema(
                  typeDefinitionRegistry, runtimeWiring);
    }
}

This looks quite complex. The graphql-java library introduces concepts such as schema parsing, type definition registration, and runtime wiring. Service functions must be written as DataFetchers and obtain frontend parameters via DataFetchingEnvironment. However, this style exposes the underlying implementation of the GraphQL engine and is somewhat outdated.

In the latest Spring GraphQL framework, only a few simple annotations are needed—the framework handles the complexity automatically:

@Controller
public class BookController {

    @QueryMapping
    public Book getBookById(@Argument Long id) {
        // ...
    }
}

With this encapsulation, business code only needs a small number of annotations and POJOs; there is generally no need to use internal graphql-java interfaces. The framework automatically analyzes Java class definitions and generates GraphQL type definitions, eliminating manual schema maintenance.

The Quarkus framework offers similarly mature support for GraphQL and introduced comparable annotation mechanisms before Spring:

@ApplicationScoped
@GraphQLApi
public class BookService {

    @Query("getBookById")
    public Book getBookById(@Name("id") Long id) {
        return ...
    }
}

The Nop Platform’s NopGraphQL framework also uses annotations to mark service functions:

@BizModel("Book")
public class BookBizModel{
   @BizQuery
   public Book getBookById(@Name("id") Long id){
     ...
   }
}

On top of basic GraphQL service support, NopGraphQL integrates with the NopORM data access engine through CrudBizModel, providing comprehensive support for common CRUD operations (including complex paginated queries, sub‑table filtering, and master‑detail updates) typically without additional code.

For a detailed introduction, see the article “Feature Comparison Between the Nop Platform and APIJSON”.

GraphQL’s design is purer than traditional web frameworks; it does not introduce concepts bound to the web runtime environment such as Request and Response, making it easy to achieve a fully POJO‑oriented encapsulation. However, GraphQL returns a fixed JSON format and cannot implement file upload/download functionality directly. NopGraphQL adds a /f/upload extension for this, making its semantics more complete.

See the Bilibili video: “How the Nop Platform Adds File Upload/Download Support to GraphQL”.

II. Extending GraphQL to Return Status Codes

The overall design of the GraphQL protocol is fairly complete, especially with many built‑in extensibility features. The Nop Platform uses the extensions map in GraphQLResponse to store additional return status codes:

@DataBean
public class GraphQLResponseBean {
    List errors;
    Object data;
    Map extensions;

    @JsonIgnore
    public String getErrorCode() {
        return (String) getExtension("nop-error-code");
    }

    @JsonIgnore
    public int getStatus() {
        if (extensions == null)
            return 0;
        int defaultStatus = hasError() ? -1 : 0;
        return ConvertHelper.toPrimitiveInt(
            extensions.get("nop-status"),
            defaultStatus,
            NopException::new);
    }
}

Leveraging GraphQL directives as an extension mechanism, the NopGraphQL framework also introduces more application‑related features to simplify typical application services. For details, see “Nop Primer: How to Creatively Extend GraphQL”.

III. The Equivalence of GraphQL and REST

On the surface, GraphQL provides many advanced features that go beyond traditional REST services. Interestingly, rigorous theoretical analysis reveals that GraphQL is mathematically equivalent to augmenting a conventional REST service with a special @selection parameter for selecting result fields. The Nop Platform establishes the following equivalence between a GraphQL request and a REST request, allowing the same backend service function to be accessed via both protocols.

query{
  Book__get(id: 123) { name, title }
}

Equivalent to

/r/Book__get?id=123&@selection=name,title

From this perspective, GraphQL is merely a pull‑mode REST call. In the Nop Platform implementation… (content continues).

Back to Blog

Related posts

Read more »