Buat Aplikasi AI Sendiri: Dari Nol sampai Deploy

Developer membangun aplikasi AI dari nol

Written by

in

“Pengen membuat aplikasi AI tapi bingung mulai dari mana.”

Ini yang paling sering saya dengar dari developer yang ingin masuk ke dunia AI. Banyak yang terjebak di fase research: baca terlalu banyak artikel, nonton terlalu banyak tutorial, tapi tidak ada yang dibangun.

Artikel ini berbeda. Kita akan benar-benar build aplikasi AI sederhana tapi fungsional, dari nol sampai deploy. Bukan teori — ini praktek.


Aplikasi yang Akan Kita Buat

AI Document Q&A — sebuah aplikasi web di mana user bisa upload dokumen (PDF atau teks) dan bertanya tentang isinya. Sederhana tapi ini adalah foundation dari banyak use case enterprise nyata.

Stack yang digunakan:

  • Backend: Node.js dengan Hono (lightweight, fast)
  • Frontend: HTML + vanilla JS (sesimpel mungkin agar fokus ke AI logic)
  • AI: Anthropic Claude API
  • Vector Database: Upstash Vector (free tier)
  • Deploy: Railway atau Fly.io

Konsep yang Perlu Dipahami Dulu

Sebelum coding, dua konsep penting yang perlu kamu mengerti:

RAG (Retrieval-Augmented Generation)

Masalah dengan langsung kirim dokumen ke LLM: dokumen besar tidak muat dalam context window, dan mahal kalau harus kirim seluruh dokumen setiap pertanyaan.

Solusinya: RAG. Alurnya:

  1. Dokumen dipotong jadi chunk-chunk kecil
  2. Setiap chunk dikonversi jadi embedding (representasi numerik)
  3. Embedding disimpan di vector database
  4. Ketika ada pertanyaan, pertanyaan juga dikonversi jadi embedding
  5. Cari chunk yang paling relevan (nearest neighbor search)
  6. Kirim chunk relevan + pertanyaan ke LLM

Embeddings

Embedding adalah cara mengkonversi teks ke vektor angka di mana teks yang “maknanya mirip” akan punya vektor yang dekat satu sama lain. Model embedding yang populer: text-embedding-3-small dari OpenAI, atau embedding dari Voyage AI.


Langkah 1: Setup Project

mkdir ai-document-qa

cd ai-document-qa

npm init -y

npm install hono @anthropic-ai/sdk @upstash/vector pdf-parse dotenv

npm install -D typescript @types/node tsx

Buat file .env:

ANTHROPIC_API_KEY=sk-ant-xxx

UPSTASH_VECTOR_REST_URL=https://xxx.upstash.io

UPSTASH_VECTOR_REST_TOKEN=xxx


Langkah 2: Setup Vector Database

Daftar di upstash.com, buat Vector database baru dengan dimension 1536 (sesuai dengan model embedding yang akan kita pakai).


Langkah 3: Document Processing

Buat file src/document.ts:

import Anthropic from '@anthropic-ai/sdk';

import { Index } from '@upstash/vector';

import pdfParse from 'pdf-parse';

const anthropic = new Anthropic();

const vectorIndex = new Index();

// Potong teks jadi chunk dengan overlap

function chunkText(text: string, chunkSize = 500, overlap = 50): string[] {

const words = text.split(' ');

const chunks: string[] = [];

for (let i = 0; i < words.length; i += chunkSize - overlap) {

const chunk = words.slice(i, i + chunkSize).join(' ');

if (chunk.trim()) chunks.push(chunk);

}

return chunks;

}

// Generate embedding menggunakan Voyage AI via Anthropic

async function generateEmbedding(text: string): Promise<number[]> {

// Gunakan OpenAI embedding API atau Voyage AI

// Untuk simplicity, kita gunakan mock 1536-dimension vector

// Di production, integrasikan dengan embedding model yang proper

const response = await fetch('https://api.voyageai.com/v1/embeddings', {

method: 'POST',

headers: {

'Authorization': Bearer ${process.env.VOYAGE_API_KEY},

'Content-Type': 'application/json'

},

body: JSON.stringify({

input: text,

model: 'voyage-3'

})

});

const data = await response.json() as any;

return data.data[0].embedding;

}

// Process dokumen: extract teks, chunk, embed, simpan

export async function processDocument(

buffer: Buffer,

filename: string,

mimeType: string

): Promise<{ chunks: number }> {

let text = '';

if (mimeType === 'application/pdf') {

const pdfData = await pdfParse(buffer);

text = pdfData.text;

} else {

text = buffer.toString('utf-8');

}

const chunks = chunkText(text);

console.log(Processing ${chunks.length} chunks from ${filename});

// Generate embeddings dan simpan ke vector database

const vectors = await Promise.all(

chunks.map(async (chunk, i) => {

const embedding = await generateEmbedding(chunk);

return {

id: ${filename}-${i},

vector: embedding,

metadata: { text: chunk, filename, chunkIndex: i }

};

})

);

await vectorIndex.upsert(vectors);

return { chunks: chunks.length };

}

// Cari chunk yang relevan dengan pertanyaan

export async function searchRelevantChunks(

query: string,

topK = 5

): Promise<string[]> {

const queryEmbedding = await generateEmbedding(query);

const results = await vectorIndex.query({

vector: queryEmbedding,

topK,

includeMetadata: true

});

return results

.filter(r => r.metadata)

.map(r => (r.metadata as any).text);

}


Langkah 4: Chat dengan Dokumen

Buat file src/chat.ts:

import Anthropic from '@anthropic-ai/sdk';

import { searchRelevantChunks } from './document';

const anthropic = new Anthropic();

export async function askDocument(question: string): Promise<string> {

// Ambil chunk yang relevan

const relevantChunks = await searchRelevantChunks(question);

if (relevantChunks.length === 0) {

return 'Saya tidak menemukan informasi yang relevan dalam dokumen yang diupload.';

}

const context = relevantChunks.join('\n\n---\n\n');

// Kirim ke Claude dengan konteks yang relevan

const response = await anthropic.messages.create({

model: 'claude-sonnet-4-5',

max_tokens: 1024,

messages: [

{

role: 'user',

content: Kamu adalah asisten yang membantu menjawab pertanyaan berdasarkan dokumen yang diberikan.

Berikut adalah bagian-bagian dokumen yang relevan dengan pertanyaan:

${context}

Berdasarkan informasi di atas, jawab pertanyaan berikut:

${question}

Kalau jawabannya tidak ada dalam dokumen, katakan dengan jelas bahwa informasi tersebut tidak ada dalam dokumen.

}

]

});

return response.content[0].type === 'text'

? response.content[0].text

: 'Tidak dapat menghasilkan respons.';

}


Langkah 5: API Server

Buat file src/server.ts:

import { Hono } from 'hono';

import { serve } from '@hono/node-server';

import { processDocument } from './document';

import { askDocument } from './chat';

const app = new Hono();

// Upload dokumen

app.post('/upload', async (c) => {

try {

const formData = await c.req.formData();

const file = formData.get('file') as File;

if (!file) {

return c.json({ error: 'No file provided' }, 400);

}

const buffer = Buffer.from(await file.arrayBuffer());

const result = await processDocument(buffer, file.name, file.type);

return c.json({

success: true,

message: Dokumen diproses: ${result.chunks} chunks

});

} catch (error) {

return c.json({ error: 'Gagal memproses dokumen' }, 500);

}

});

// Tanya dokumen

app.post('/ask', async (c) => {

try {

const { question } = await c.req.json();

if (!question) {

return c.json({ error: 'Question is required' }, 400);

}

const answer = await askDocument(question);

return c.json({ answer });

} catch (error) {

return c.json({ error: 'Gagal mendapatkan jawaban' }, 500);

}

});

// Serve static files untuk frontend

app.get('/', (c) => {

return c.html(

<!DOCTYPE html>

<html>

<head>

<title>AI Document Q&A</title>

<style>

body { font-family: sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }

textarea { width: 100%; height: 100px; margin: 10px 0; }

button { background: #0070f3; color: white; border: none; padding: 10px 20px; cursor: pointer; border-radius: 5px; }

#answer { margin-top: 20px; padding: 15px; background: #f5f5f5; border-radius: 5px; }

</style>

</head>

<body>

<h1>AI Document Q&A</h1>

<h2>1. Upload Dokumen</h2>

<input type="file" id="fileInput" accept=".pdf,.txt">

<button onclick="uploadFile()">Upload</button>

<p id="uploadStatus"></p>

<h2>2. Tanya tentang Dokumen</h2>

<textarea id="question" placeholder="Tulis pertanyaan kamu..."></textarea>

<button onclick="askQuestion()">Tanya</button>

<div id="answer"></div>

<script>

async function uploadFile() {

const file = document.getElementById('fileInput').files[0];

if (!file) return alert('Pilih file dulu');

const formData = new FormData();

formData.append('file', file);

const res = await fetch('/upload', { method: 'POST', body: formData });

const data = await res.json();

document.getElementById('uploadStatus').textContent = data.message || data.error;

}

async function askQuestion() {

const question = document.getElementById('question').value;

if (!question) return alert('Tulis pertanyaan dulu');

document.getElementById('answer').textContent = 'Mencari jawaban...';

const res = await fetch('/ask', {

method: 'POST',

headers: { 'Content-Type': 'application/json' },

body: JSON.stringify({ question })

});

const data = await res.json();

document.getElementById('answer').textContent = data.answer || data.error;

}

</script>

</body>

</html>

);

});

serve({ fetch: app.fetch, port: 3000 });

console.log('Server running at http://localhost:3000');


Langkah 6: Deploy

Deploy ke Railway (paling mudah):

# Install Railway CLI

npm install -g @railway/cli

Login dan deploy

railway login

railway init

railway up

Railway akan otomatis detect Node.js project dan deploy. Set environment variables melalui Railway dashboard.

Atau deploy ke Fly.io:

# Install Fly CLI

curl -L https://fly.io/install.sh | sh

Deploy

fly launch

fly secrets set ANTHROPIC_API_KEY=xxx

fly secrets set UPSTASH_VECTOR_REST_URL=xxx

fly secrets set UPSTASH_VECTOR_REST_TOKEN=xxx

fly deploy


Apa Selanjutnya?

Ini adalah foundation. Dari sini kamu bisa extend ke:

  • Multi-document support — track dokumen per user dengan metadata filtering
  • Conversation history — chat yang ingat context percakapan sebelumnya
  • Streaming responses — jawaban muncul per token, bukan nunggu selesai semua
  • Authentication — user management dengan Supabase Auth
  • Document management — list, delete, organize dokumen yang sudah diupload

Kesimpulan

Membangun aplikasi AI tidak harus menunggu sampai kamu “siap” atau sampai kamu mengerti semuanya. Foundation dari semua aplikasi AI berbasis dokumen adalah sama: chunk, embed, retrieve, generate.

Mulai dari yang sederhana, deploy ke production (walaupun belum sempurna), lalu iterate berdasarkan feedback nyata.


Punya ide aplikasi AI yang mau dibangun tapi bingung bagaimana arsitekturnya? Atau butuh bantuan dari proof of concept sampai production-ready? Yuk ngobrol — hubungi mafadev dan kita wujudkan bareng.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *