Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
19 Jul 2025 · 6 mnt baca ·Artikel 19 / 125
Go

19 Implementasi Resolver untuk Query `users`

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

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:

js
1// resolvers.js
2const resolvers = {
3  Query: {
4    users: () => db.users.findAll()
5  }
6};

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.

js
1const resolvers = {
2  Query: {
3    users: () => db.users.findAll()
4  }
5};

2. Dengan Filter Sederhana

Menambahkan filter berdasarkan field tertentu.

js
1const resolvers = {
2  Query: {
3    users: (_, { role }) => db.users.findAll({ where: { role } })
4  }
5};

3. Pagination (Limit & Offset)

Mengimplementasikan limitasi data agar tidak mengembalikan semua record.

js
1const resolvers = {
2  Query: {
3    users: (_, { limit = 10, offset = 0 }) =>
4      db.users.findAll({ limit, offset })
5  }
6};

4. Pencarian (Search Query)

Menambahkan kemampuan pencarian berdasarkan nama, email, dsb.

js
 1const resolvers = {
 2  Query: {
 3    users: (_, { search }) =>
 4      db.users.findAll({
 5        where: {
 6          name: { [Op.iLike]: `%${search}%` }
 7        }
 8      })
 9  }
10};

5. Ordering/Sorting

Memberikan kemampuan urutan data pada hasil query.

js
 1const resolvers = {
 2  Query: {
 3    users: (_, { orderBy = "name_ASC" }) =>
 4      db.users.findAll({ order: parseOrderBy(orderBy) })
 5  }
 6};
 7
 8function parseOrderBy(orderBy) {
 9  const [field, direction] = orderBy.split("_");
10  return [[field, direction]];
11}

6. Auth Middleware (Authentication & Authorization)

Melindungi query agar hanya user tertentu bisa mendapat data.

js
1const resolvers = {
2  Query: {
3    users: (_, __, { user }) => {
4      if (!user || !user.isAdmin) throw new Error("Unauthorized");
5      return db.users.findAll();
6    }
7  }
8};

7. Dynamic Field Selection

Mengoptimalkan query berdasarkan field yang diminta klien.

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

8. Caching Layer

Menggunakan cache agar query lebih efisien.

js
1users: async () => {
2  const cacheKey = "users:all";
3  let usersData = await redis.get(cacheKey);
4  if (usersData) return JSON.parse(usersData);
5
6  usersData = await db.users.findAll();
7  await redis.set(cacheKey, JSON.stringify(usersData), 'EX', 60); // cache 1 menit
8  return usersData;
9}

9. DataLoader Pattern (Batching + Caching)

Mengurangi query N+1 dengan DataLoader.

js
1// resolver
2users: (_, __, { loaders }) => loaders.userLoader.loadMany(ids)
Danger
Pastikan Anda menggunakan per-request cache untuk DataLoader.

10. Error Handling & Logging

Membungkus query dengan error handling & logging.

js
1users: async () => {
2  try {
3    return await db.users.findAll();
4  } catch (error) {
5    logger.error(error)
6    throw new Error("Failed to load users");
7  }
8}

11. Soft Delete Awareness

Menghindari user yang sudah dihapus secara soft delete.

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

12. External API as Data Source

Mengambil data user dari API eksternal.

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

13. Field-level Authorization

Melindungi field tertentu dalam objek user.

js
1User: {
2  email: (user, _, { user: currentUser }) => 
3    currentUser.isAdmin ? user.email : null
4}

14. Derived Data (Computed Fields)

Menambahkan field hasil komputasi.

js
1User: {
2  fullname: (user) => `${user.firstName} ${user.lastName}`
3}

15. Aggregated Fields

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

js
1User: {
2  postCount: (user) =>
3    db.posts.count({ where: { userId: user.id } })
4}

16. Multi-Tenant Context

Menyiapkan resolver agar aware siapa tenant pemilik data.

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

17. Multi-Source Merge

Menggabungkan data dari banyak sumber.

js
1users: async () => {
2  const [main, legacy] = await Promise.all([
3    db.users.findAll(),
4    legacyApi.getUsers()
5  ]);
6  return mergeUsers(main, legacy);
7}

18. Rate Limiting

Membatasi seberapa sering query dapat dieksekusi.

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

19. Custom Business Logic/Rule

Menambahkan aturan bisnis custom sebelum return data.

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

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.

MERMAID
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:

Artikel Terkait

💬 Komentar