Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
02 Aug 2025 · 6 min read ·Article 33 / 125
Go

33 Writing Query Resolvers from the Database

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

33 Writing Query Resolvers from the Database

In the modern era of application development, backend architecture increasingly caters to the client’s need for efficient and flexible data access. One of the most up-to-date approaches is using GraphQL as the query language for an API. On the backend side, we are often faced with the need to write resolvers so that data in the database can be queried easily by clients. This article will break down how to write 33 query resolvers from the database, using a systematic and scalable approach.

We will move from the fundamental concepts, to code examples, flow simulations, and even a simple benchmark. Assumption: the backend uses Node.js with Apollo Server and the Sequelize ORM as the data layer to a relational database (PostgreSQL).


What Is a Query Resolver?

A GraphQL resolver is a function that handles fetching data from a source (usually a database) based on the client’s query request. A resolver can handle fetching a single row, multiple rows, filtering, sorting, and even data relationships.

Every field in a GraphQL schema that needs data from the database usually has its own resolver.


Designing the Model and Database Schema

Let’s imagine a simple database for an online store application:

TableFields
Userid, name, email
Productid, name, price, stock
Orderid, userId, productId, quantity, createdAt

Let’s assume there are 33 query scenarios we want to support, such as:

  • Query user by id
  • Query all users
  • Query user by email
  • Query product by id
  • Query products by name
  • Query all products sorted by price, and so on.

Query Resolver Flow Diagram

To understand the process, here is the query resolver flow written in mermaid code:

MERMAID
flowchart TD
    A[Client queries GraphQL] --> B[GraphQL Server menerima query]
    B --> C[Parsing & Validating Query]
    C --> D[Menemukan Resolver]
    D --> E[Resolver Fetch ke DB lewat ORM]
    E --> F[ORM Kirim Data ke Resolver]
    F --> G[Resolver Format Data]
    G --> H[Balas ke Client]

Example GraphQL Schema

graphql
 1type Query {
 2  userById(id: ID!): User
 3  users: [User]
 4  userByEmail(email: String!): User
 5  productById(id: ID!): Product
 6  productsByName(name: String!): [Product]
 7  allProducts(sortByPrice: Boolean): [Product]
 8  # ...up to 33 query endpoints
 9}
10
11type User {
12  id: ID
13  name: String
14  email: String
15  orders: [Order]
16}
17
18type Product {
19  id: ID
20  name: String
21  price: Float
22  stock: Int
23}
24
25type Order {
26  id: ID
27  user: User
28  product: Product
29  quantity: Int
30  createdAt: String
31}

Resolver File Structure & Best Practices

To keep things maintainable, it’s best to split resolvers by resource/domain:

text
1resolvers/
2  ├─ index.js
3  ├─ userResolver.js
4  ├─ productResolver.js
5  └─ orderResolver.js

And each file has its own resolutions.


Implementing Resolvers with Sequelize

Let’s jump straight into an implementation example. For instance: userResolver.js

js
 1// userResolver.js
 2const { User, Order } = require('../models');
 3
 4const userResolver = {
 5  Query: {
 6    userById: async (_, { id }) => {
 7      return await User.findByPk(id);
 8    },
 9    users: async () => {
10      return await User.findAll();
11    },
12    userByEmail: async (_, { email }) => {
13      return await User.findOne({ where: { email } });
14    }
15  },
16  User: {
17    orders: async (parent) => {
18      return await Order.findAll({ where: { userId: parent.id } });
19    }
20  }
21};
22
23module.exports = userResolver;

A few notes:

  • Each function returns a promise, ready for async-await.
  • The use of parent in the field resolver maintains the relationship.

Adding Resolvers for 33 Queries

To manage 33 resolvers, use a generator pattern or a template. For example, in productResolver.js:

js
 1const { Product } = require('../models');
 2
 3const productResolver = {
 4  Query: {
 5    productById: async (_, { id }) => await Product.findByPk(id),
 6    productsByName: async (_, { name }) =>
 7      await Product.findAll({ where: { name } }),
 8    allProducts: async (_, { sortByPrice }) =>
 9      await Product.findAll({
10        order: sortByPrice ? [['price', 'ASC']] : undefined
11      }),
12    // ...Add 30 more as per requirements
13  }
14};
15
16module.exports = productResolver;

For scalability and DRY (Don’t Repeat Yourself), create a utility to automate several resolvers:

js
1function createSimpleFindQuery(model, by) {
2  return async (_, args) => await model.findOne({ where: { [by]: args[by] } });
3}

Query Simulation: User with Orders

Suppose the user wants User data along with the list of Orders:

graphql
 1query {
 2  userById(id: "123") {
 3    id
 4    name
 5    orders {
 6      id
 7      product {
 8        name
 9        price
10      }
11      quantity
12      createdAt
13    }
14  }
15}

How does the resolver handle this?

  • First it fetches the User via ID (userById)
  • When the orders field is accessed, the User.orders resolver is triggered to fetch the orders
  • The product field within Order will trigger a separate resolver on the Order type

Simulated Output from the 33 Queries

Here is a miniature simulation of the output for 3 of the 33 queries:

QueryParameterExample Output
userByIdid: 123{“id”:123, “name”:“Rafi”, “email”:“rafi@foo.com ”}
userByEmailemail:foo@bar.com{“id”:99, “name”:“Siti”, “email”:“foo@bar.com ”}
allProducts(sortByPrice: true)sortByPrice: true[{“id”:11,“name”:“T-shirt”,“price”:99.9,…}, …]

Rules & Tips for Writing Many Resolvers

  1. DRY Principle: If there are similar queries, use a helper or generator.
  2. Validation: Validate parameters before hitting the DB to minimize errors and database risk.
  3. Batching & Caching: For nested resolvers, use DataLoader to prevent the N+1 query problem.
  4. Error Handling: Use try-catch in every resolver and keep the error format consistent.
  5. Documentation: Add JSDoc or at least a minimal comment for the endpoint/purpose of each resolver.
  6. Testing: Prepare dedicated unit/integration tests for every resolver.

Simple Benchmark

Suppose 33 queries are run in parallel by the client. Use Promise.all to test them simultaneously:

js
1const allQueries = [
2  gql1, gql2, /* ... up to gql33 */
3];
4
5Promise.all(allQueries.map(q => apolloServer.executeOperation({ query: q })))
6  .then(results => {
7    // Evaluate time taken, errors, etc.
8  });

Conclusion

Writing 33 query resolvers is indeed challenging, especially when it comes to keeping the code scalable & maintainable. Focus on resolver structure, automate similar queries, and apply GraphQL best practices. Don’t forget testing and documentation, so that your team can maintain & evolve the codebase sustainably.

If you have any custom experience or tips for writing resolvers, feel free to share them in the comments! Happy coding 🚀

Related Articles

💬 Comments