2026-09-16 16:51:36 +07:00

159 lines
4.3 KiB
JavaScript

const { ApolloServer } = require('@apollo/server');
const { startServerAndCreateNextHandler } = require('@as-integrations/next');
const { Pool } = require('pg');
// 1. Inisialisasi Pool PostgreSQL untuk Serverless
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false,
},
max: 1,
});
// 2. Definisi Schema GraphQL
const typeDefs = `#graphql
type Category {
id: ID!
name: String!
description: String
products: [Product!]!
}
type Product {
id: ID!
title: String!
price: Float!
category_id: Int
created_by: ID
category: Category
}
type Query {
categories: [Category!]!
category(id: ID!): Category
products: [Product!]!
product(id: ID!): Product
}
`;
// 3. Plugin Apollo untuk menyuntikkan log counter ke response extensions
const metricsPlugin = {
async requestDidStart() {
return {
async willSendResponse({ response, contextValue }) {
if (response.body.kind === 'single') {
response.body.singleResult.extensions = {
...response.body.singleResult.extensions,
nPlusOneMetrics: {
totalCalls: contextValue.metrics.categoryProductsCalls,
resolverLogs: contextValue.metrics.callLogs,
},
};
}
},
};
},
};
// 4. Definisi Resolver
const resolvers = {
Query: {
categories: async () => {
const result = await pool.query('SELECT * FROM categories ORDER BY id ASC');
return result.rows;
},
category: async (_, { id }) => {
const result = await pool.query('SELECT * FROM categories WHERE id = $1', [id]);
return result.rows[0] || null;
},
products: async () => {
const result = await pool.query(
'SELECT id, title, price::float, category_id, created_by FROM products ORDER BY id ASC'
);
return result.rows;
},
product: async (_, { id }) => {
const result = await pool.query(
'SELECT id, title, price::float, category_id, created_by FROM products WHERE id = $1',
[id]
);
return result.rows[0] || null;
},
},
// Resolver Relasi Category -> Products (One-to-Many)
Category: {
products: async (parent, _, contextValue) => {
// Increment counter di context request saat ini
contextValue.metrics.categoryProductsCalls += 1;
const currentCall = contextValue.metrics.categoryProductsCalls;
// Simpan log urutan pemanggilan ke array context
contextValue.metrics.callLogs.push(
`[resolver counter] Category.products dipanggil ${currentCall} kali (category_id=${parent.id})`
);
const result = await pool.query(
'SELECT id, title, price::float, category_id, created_by FROM products WHERE category_id = $1 ORDER BY id ASC',
[parent.id]
);
return result.rows;
},
},
// Resolver Relasi Product -> Category (Many-to-One)
Product: {
category: async (parent) => {
if (!parent.category_id) return null;
const result = await pool.query('SELECT * FROM categories WHERE id = $1', [parent.category_id]);
return result.rows[0] || null;
},
},
};
// 5. Inisialisasi Apollo Server dengan Plugin
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true,
csrfPrevention: false,
plugins: [metricsPlugin],
});
// 6. Inisialisasi Handler dengan Context Per-Request
const apolloHandler = startServerAndCreateNextHandler(server, {
context: async () => ({
metrics: {
categoryProductsCalls: 0,
callLogs: [],
},
}),
});
// 7. Handler CORS dan Serverless Entrypoint
module.exports = async function handler(req, res) {
const allowedOrigins = new Set([
'https://studio.apollographql.com',
'https://embeddable-sandbox.cdn.apollographql.com',
]);
const origin = req.headers.origin;
if (allowedOrigins.has(origin)) {
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Vary', 'Origin');
}
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
res.setHeader(
'Access-Control-Allow-Headers',
'Content-Type, Authorization, apollo-require-preflight, x-apollo-operation-name'
);
if (req.method === 'OPTIONS') {
res.status(200).end();
return;
}
return apolloHandler(req, res);
};