tutorial

19 Implementasi Resolver untuk Query users

19 Implementasi Resolver untuk Query users

Pada artikel ini, saya ingin mengupas secara mendalam tentang berbagai cara mengimplementasikan resolver GraphQL untuk query users. Dengan mengombinasikan pengalaman mengelola aplikasi skala menengah hingga besar, saya telah menemukan berbagai variasi, pola, dan teknik yang umum atau bahkan canggih digunakan dalam menangani permintaan data pengguna. Mari kita kupas 19 pendekatan implementasi resolver users secara praktis, dilengkapi dengan kode, simulasi, tabel, serta diagram alur.


Overview: Apa Itu Resolver?

Untuk memulai, kita perlu paham dulu apa itu resolver dalam konteks GraphQL. Resolver adalah fungsi bertanggung jawab mengambil data yang diminta oleh query klien dan mengembalikannya ke GraphQL server. Contoh klasik resolver untuk endpoint users:

// resolvers.js
const resolvers = {
  Query: {
    users: () => db.users.findAll()
  }
};

Namun, implementasinya bisa jauh lebih kompleks, tergantung kebutuhan aplikasi.


19 Implementasi Resolver users

Berikut 19 pendekatan tersebut, disusun dari yang paling dasar hingga dapat menangani edge-case kompleks.

1. Query Sederhana

Implementasi dasar, mengambil semua user tanpa filter apa pun.

const resolvers = {
  Query: {
    users: () => db.users.findAll()
  }
};

2. Dengan Filter Sederhana

Menambahkan filter berdasarkan field tertentu.

const resolvers = {
  Query: {
    users: (_, { role }) => db.users.findAll({ where: { role } })
  }
};

3. Pagination (Limit & Offset)

Mengimplementasikan limitasi data agar tidak mengembalikan semua record.

const resolvers = {
  Query: {
    users: (_, { limit = 10, offset = 0 }) =>
      db.users.findAll({ limit, offset })
  }
};

4. Pencarian (Search Query)

Menambahkan kemampuan pencarian berdasarkan nama, email, dsb.

const resolvers = {
  Query: {
    users: (_, { search }) =>
      db.users.findAll({
        where: {
          name: { [Op.iLike]: `%${search}%` }
        }
      })
  }
};

5. Ordering/Sorting

Memberikan kemampuan urutan data pada hasil query.

const resolvers = {
  Query: {
    users: (_, { orderBy = "name_ASC" }) =>
      db.users.findAll({ order: parseOrderBy(orderBy) })
  }
};

function parseOrderBy(orderBy) {
  const [field, direction] = orderBy.split("_");
  return [[field, direction]];
}

6. Auth Middleware (Authentication & Authorization)

Melindungi query agar hanya user tertentu bisa mendapat data.

const resolvers = {
  Query: {
    users: (_, __, { user }) => {
      if (!user || !user.isAdmin) throw new Error("Unauthorized");
      return db.users.findAll();
    }
  }
};

7. Dynamic Field Selection

Mengoptimalkan query berdasarkan field yang diminta klien.

users: async (_, __, ___, info) => {
  const fields = info.fieldNodes[0].selectionSet.selections.map(s => s.name.value);
  return db.users.findAll({ attributes: fields });
}

8. Caching Layer

Menggunakan cache agar query lebih efisien.

users: async () => {
  const cacheKey = "users:all";
  let usersData = await redis.get(cacheKey);
  if (usersData) return JSON.parse(usersData);

  usersData = await db.users.findAll();
  await redis.set(cacheKey, JSON.stringify(usersData), 'EX', 60); // cache 1 menit
  return usersData;
}

9. DataLoader Pattern (Batching + Caching)

Mengurangi query N+1 dengan DataLoader.

// resolver
users: (_, __, { loaders }) => loaders.userLoader.loadMany(ids)

Pastikan Anda menggunakan per-request cache untuk DataLoader.

10. Error Handling & Logging

Membungkus query dengan error handling & logging.

users: async () => {
  try {
    return await db.users.findAll();
  } catch (error) {
    logger.error(error)
    throw new Error("Failed to load users");
  }
}

11. Soft Delete Awareness

Menghindari user yang sudah dihapus secara soft delete.

users: () => db.users.findAll({ where: { deletedAt: null } })

12. External API as Data Source

Mengambil data user dari API eksternal.

users: async () => {
  const response = await fetch("https://external-api.com/users");
  return response.json();
}

13. Field-level Authorization

Melindungi field tertentu dalam objek user.

User: {
  email: (user, _, { user: currentUser }) => 
    currentUser.isAdmin ? user.email : null
}

14. Derived Data (Computed Fields)

Menambahkan field hasil komputasi.

User: {
  fullname: (user) => `${user.firstName} ${user.lastName}`
}

15. Aggregated Fields

Menambahkan field aggregate (mis: jumlah postingan per user).

User: {
  postCount: (user) =>
    db.posts.count({ where: { userId: user.id } })
}

16. Multi-Tenant Context

Menyiapkan resolver agar aware siapa tenant pemilik data.

users: (_, __, { tenantId }) => 
  db.users.findAll({ where: { tenantId } })

17. Multi-Source Merge

Menggabungkan data dari banyak sumber.

users: async () => {
  const [main, legacy] = await Promise.all([
    db.users.findAll(),
    legacyApi.getUsers()
  ]);
  return mergeUsers(main, legacy);
}

18. Rate Limiting

Membatasi seberapa sering query dapat dieksekusi.

users: async (_, __, { user }) => {
  if (await isRateLimited(user.id, 'users')) {
    throw new Error("Rate limit exceeded");
  }
  return db.users.findAll();
}

19. Custom Business Logic/Rule

Menambahkan aturan bisnis custom sebelum return data.

users: async (_, args, context) => {
  const users = await db.users.findAll();
  return users.filter(u => customBusinessRule(u, context));
}

Simulasi: Membandingkan Respons

Misalkan ada 3 user sulap data berikut:

idnameroledeletedAt
1Aliceadminnull
2Bobeditornull
3Carolviewer2024-01-01

Query GraphQL dasar akan return: Alice, Bob, Carol.

Dengan Soft Delete Awareness (deletedAt: null) hanya akan return: Alice, Bob.


Diagram Alur Resolver users (mermaid)

Mari visualisasikan bagaimana pipeline resolver dengan beberapa concern di atas.

graph TD
    A[Received Query: users] --> B{Is Authenticated?}
    B -- No --> Z[Throw Unauthorized Error]
    B -- Yes --> C{Is Rate Limit Exceeded?}
    C -- Yes --> Y[Throw Rate Limit Error]
    C -- No --> D[Check Cache]
    D -- Cache Hit --> E[Return Cached Data]
    D -- Miss --> F[Fetch From DB/API]
    F --> G{Apply Filter/Soft Delete}
    G --> H[Transform / Map Custom Logic]
    H --> I[Store To Cache(if enabled)]
    I --> J[Return Data]

Kapan Menggunakan Pola Tertentu?

ImplementasiCocok UntukKelebihanKekurangan
PaginationData besarLebih efisienAgak kompleks
AuthorizationData sensitifLebih amanPerlu pengelolaan session
Caching/DataLoaderBeban tinggi/N+1 masalahPerforman, hemat sumberdayaMenambah layer
AggregationData insight/statistikInsightfulQuery lebih berat
Multi-tenantSaaS/berbasis klienIsolasi dataPerlu context tenant

Penutup

Sayangnya, tidak ada “one-size-fits-all” dalam membangun resolver GraphQL. Tergantung kebutuhan aplikasi, kebijakan bisnis, dan karakteristik data, Anda bisa memilih, memodifikasi, atau bahkan mengombinasikan berbagai implementasi di atas.

Justru di situlah seni seorang engineer: Menyusun pipeline resolver yang efisien, aman, dan scalable, dengan tetap menjaga fleksibilitas dan maintainability. Semoga referensi ini menginspirasi Anda membangun resolver users yang solid, tidak hanya sekedar “mengembalikan array user”.

Bagaimana dengan implementasi resolver Anda? Sudah pakai optimasi apa saja? Silakan share pengalaman dan strategi unik Anda di komentar!


Referensi tambahan:

comments powered by Disqus

Topik Terhangat

programming
219
tutorial
86
tips-and-trick
43
jaringan
28
hardware
11
linux
4
kubernetes
1