feat: setup graphql server with neon relation resolvers

This commit is contained in:
MelvynFaith 2026-09-09 16:44:06 +07:00
commit 4dc4a1f716
5 changed files with 1673 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules
.env

99
index.js Normal file
View File

@ -0,0 +1,99 @@
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const { Pool } = require('pg');
require('dotenv').config();
// 1. Inisialisasi Pool PostgreSQL
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false,
},
});
// 2. Definisi Schema GraphQL (typeDefs)
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. Definisi Resolver (Query + Nested Resolvers)
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 () => {
// price di-cast ke float agar cocok dengan tipe Float di GraphQL
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) => {
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;
},
},
};
// 4. Inisialisasi Apollo Server
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: true, // Wajib aktif untuk Apollo Sandbox publik
});
// 5. Jalankan Server
async function startServer() {
const port = process.env.PORT || 4000;
const { url } = await startStandaloneServer(server, {
listen: { port: Number(port) },
});
console.log(`Server GraphQL siap di: ${url}`);
}
startServer();

1527
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "week-4",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"@apollo/server": "^5.5.1",
"cors": "^2.8.6",
"dotenv": "^17.4.2",
"graphql": "^16.14.2",
"pg": "^8.23.0"
}
}

24
test-db.js Normal file
View File

@ -0,0 +1,24 @@
const { Pool } = require('pg');
require('dotenv').config();
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: {
rejectUnauthorized: false,
},
});
async function test() {
try {
const res = await pool.query('SELECT NOW() AS server_time, COUNT(*) AS total_products FROM products');
console.log('Koneksi Sukses!');
console.log('Waktu DB Neon:', res.rows[0].server_time);
console.log('Jumlah Data Produk:', res.rows[0].total_products);
} catch (err) {
console.error('Koneksi Gagal:', err.message);
} finally {
await pool.end();
}
}
test();