Build a Privacy-First Self-Hosted AI Legacy Code Translator: Modernize PHP & jQuery Codebases with Bun.js, Elysia.js, Ollama, and pgvector (2026 Step-by-Step Guide for Developers)
Legacy PHP and jQuery codebases hold critical business logic, but sending them to third-party AI tools risks data leaks and compliance violations. This 2026 step-by-step guide teaches you to build a fully self-hosted, privacy-first AI translator that modernizes legacy code using Bun.js, Elysia.js, Ollama, and pgvector—no sensitive data ever leaves your infrastructure.
Agentic Academy Labs
Author
Legacy PHP and jQuery power over 70% of the world’s existing web applications as of 2026, but modernizing these codebases comes with a hidden risk: most AI code translation tools send your proprietary logic, embedded user data, and internal business rules to third-party servers. For teams in regulated industries, or those handling sensitive intellectual property, this is a non-starter.
The solution? A fully self-hosted, privacy-first AI legacy code translator that runs entirely on your infrastructure, using open-source tools you control. In this guide, we’ll walk you through building this tool step-by-step with Bun.js, Elysia.js, Ollama, and pgvector—no external API calls, no data leaks, and translation accuracy that improves the more you use it with your specific codebase.
Why Build a Self-Hosted Legacy Code Translator?
- Eliminate data exposure: No proprietary code or sensitive user data ever leaves your servers, full compliance with GDPR, HIPAA, and other regulatory frameworks
- Full control over translation quality: Fine-tune the model and pattern library to match your team’s coding standards and custom legacy logic
- Cost efficiency: No per-token API fees for large codebases—your only cost is the on-premise hardware to run the stack
- Offline capability: Work on air-gapped systems for government, defense, or high-security projects with no internet access
- Custom pattern matching: Use pgvector to store embeddings of your existing codebase, so translations adapt to your project’s unique quirks over time
Tech Stack Overview (2026 Edition)
We’ll use a modern, high-performance stack built for speed and privacy:
- Bun.js: The ultra-fast JavaScript runtime built for 2026 workloads, with native TypeScript support, a built-in package manager, and 3x faster I/O performance than Node.js for code parsing and LLM routing tasks
- Elysia.js: A type-safe, Bun-native backend framework with built-in input validation, perfect for building API endpoints that handle code uploads, translation requests, and vector search queries
- Ollama: The leading local LLM runtime, which lets you run open-source code models like DeepSeek Coder V2 directly on your hardware with no external API calls
- pgvector: A PostgreSQL extension for vector storage, which lets you save embeddings of your legacy code patterns to improve translation accuracy for your specific project
Step 1: Set Up Your Self-Hosted Infrastructure
First, install all required dependencies on your local server or on-premise machine:
- Install Bun: Run
curl -fsSL https://bun.sh/install | bashand follow the on-screen instructions - Install PostgreSQL 16 with pgvector: On Ubuntu 24.04 LTS (the most common 2026 server OS), run
sudo apt install postgresql-16 postgresql-16-pgvector - Install Ollama: Run
curl -fsSL https://ollama.com/install.sh | sh, then pull the code translation model we’ll use:ollama pull deepseek-coder-v2:16b(this 16B parameter model offers the best balance of speed and accuracy for code translation tasks) - Initialize your database: Log into PostgreSQL with
sudo -u postgres psql, create a new database withCREATE DATABASE legacy_translator;, then connect to it and enable the pgvector extension withCREATE EXTENSION vector; - Create the
code_embeddingstable to store your code patterns and their vector embeddings:
CREATE TABLE code_embeddings (
id SERIAL PRIMARY KEY,
source_code TEXT NOT NULL,
translated_code TEXT,
source_language VARCHAR(50) NOT NULL,
target_language VARCHAR(50),
embedding vector(4096) NOT NULL
);
CREATE INDEX ON code_embeddings USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
The index ensures fast vector similarity searches even for large codebases with thousands of snippets.
Step 2: Build the Elysia.js Backend API
Create a new project directory, initialize a Bun project with bun init, and install dependencies: bun add elysia pg ollama.
Create a index.ts file for your Elysia server with the following code:
import { Elysia } from 'elysia'
import { Pool } from 'pg'
import ollama from 'ollama'
// Initialize PostgreSQL connection pool
const pgPool = new Pool({
host: 'localhost',
port: 5432,
database: 'legacy_translator',
user: 'postgres',
password: 'your_secure_password_here'
})
const app = new Elysia()
// Endpoint to translate individual code snippets
.post('/translate', async ({ body }) => {
const { legacyCode, sourceLanguage, targetLanguage } = body
// Generate vector embedding for the input legacy code
const embeddingResponse = await ollama.embeddings({
model: 'deepseek-coder-v2:16b',
prompt: legacyCode
})
const codeEmbedding = embeddingResponse.embedding
// Search pgvector for 3 most similar legacy code patterns in your library
const similarPatterns = await pgPool.query(`
SELECT translated_code, 1 - (embedding <=> $1) AS similarity
FROM code_embeddings
WHERE source_language = $2
ORDER BY embedding <=> $1
LIMIT 3
`, [JSON.stringify(codeEmbedding), sourceLanguage])
// Build a context-aware prompt for the LLM using similar patterns
const contextPrompt = similarPatterns.rows.length > 0
? `Follow these existing translation patterns from our codebase for consistency:\n${similarPatterns.rows.map(r => r.translated_code).join('\n')}\n\n`
: ''
const fullPrompt = `${contextPrompt}Translate the following ${sourceLanguage} code to ${targetLanguage}. Return only the translated code, no explanations or markdown formatting:\n${legacyCode}`
// Run translation via local Ollama model
const translation = await ollama.generate({
model: 'deepseek-coder-v2:16b',
prompt: fullPrompt,
stream: false,
options: { temperature: 0.1 } // Low temperature for consistent, accurate output
})
// Store the new translation in pgvector to improve future accuracy
const newTranslationEmbedding = await ollama.embeddings({
model: 'deepseek-coder-v2:16b',
prompt: translation.response
})
await pgPool.query(`
INSERT INTO code_embeddings (source_code, translated_code, source_language, target_language, embedding)
VALUES ($1, $2, $3, $4, $5)
`, [legacyCode, translation.response, sourceLanguage, targetLanguage, JSON.stringify(newTranslationEmbedding.embedding)])
return { translatedCode: translation.response }
})
// Endpoint to bulk upload your existing legacy codebase to build the pattern library
.post('/upload-codebase', async ({ body }) => {
const { codeSnippets, sourceLanguage } = body
let processedCount = 0
for (const snippet of codeSnippets) {
const embedding = await ollama.embeddings({
model: 'deepseek-coder-v2:16b',
prompt: snippet
})
await pgPool.query(`
INSERT INTO code_embeddings (source_code, source_language, embedding)
VALUES ($1, $2, $3)
`, [snippet, sourceLanguage, JSON.stringify(embedding.embedding)])
processedCount++
}
return { success: true, processedCount }
})
.listen(3000)
console.log('🚀 Privacy-first legacy code translator running on http://localhost:3000')
This Elysia server exposes two core endpoints: /translate for single snippet translation, and /upload-codebase to bulk import your existing legacy code to build a custom pattern library that improves translation accuracy over time. The low temperature setting ensures consistent, deterministic output for code translation, reducing hallucinations.
Step 3: Add a Simple Frontend (Optional)
To make the tool usable for non-technical team members, you can add a basic frontend. Create a public folder with an index.html file with the following code:
<!DOCTYPE html>
<html>
<head>
<title>Legacy Code Translator</title>
<style>
body { font-family: system-ui; max-width: 1200px; margin: 2rem auto; padding: 0 1rem; }
textarea { width: 100%; min-height: 300px; margin: 1rem 0; font-family: monospace; }
button { padding: 0.75rem 1.5rem; background: #2563eb; color: white; border: none; border-radius: 4px; cursor: pointer; }
.output { background: #f8fafc; padding: 1rem; border-radius: 4px; margin-top: 1rem; }
</style>
</head>
<body>
<h1>Legacy Code Translator (Privacy-First)</h1>
<select id="sourceLang">
<option value="php">PHP</option>
<option value="jquery">jQuery</option>
</select>
<select id="targetLang">
<option value="typescript">TypeScript (Node.js/Bun)</option>
<option value="react">React + TypeScript</option>
<option value="python">Python (FastAPI)</option>
</select>
<textarea id="legacyCode" placeholder="Paste your PHP or jQuery code here..."></textarea>
<button onclick="translate()">Translate Code</button>
<div id="output" class="output" style="display: none;"></div>
<script>
async function translate() {
const sourceLang = document.getElementById('sourceLang').value
const targetLang = document.getElementById('targetLang').value
const legacyCode = document.getElementById('legacyCode').value
const output = document.getElementById('output')
output.style.display = 'block'
output.innerText = 'Translating...'
const res = await fetch('http://localhost:3000/translate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ legacyCode, sourceLanguage: sourceLang, targetLanguage: targetLang })
})
const data = await res.json()
output.innerText = data.translatedCode
}
</script>
</body>
</html>
Then add static file serving to your Elysia server by adding .use(static({ assets: './public' })) right after the new Elysia() call. Now you can access the translator UI at http://localhost:3000.
Step 4: Optimize Translation Accuracy for Your Codebase
The biggest advantage of this self-hosted setup over generic AI translation tools is the pgvector pattern library. To get the best results:
- Bulk upload all your existing legacy code snippets (even untranslated ones) to the
/upload-codebaseendpoint first - For custom logic (like proprietary authentication flows, internal APIs, or domain-specific business rules), add a few manually translated examples to the pattern library first
- As you translate code, the system automatically stores new snippets, so translation quality improves the more you use it
For example, if your PHP codebase uses a custom Auth::check() helper, the vector search will pull that pattern when translating similar authentication code, so the output uses your target stack’s equivalent helper instead of a generic implementation.
Step 5: Privacy & Security Best Practices
Since this tool is designed for privacy-first use, follow these guidelines to keep your code safe:
- Restrict API access: Add Elysia’s built-in API key middleware to block unauthorized requests to your translation endpoints
- Bind Ollama to localhost only: Edit the Ollama config file at
/etc/ollama/config.jsonto set"host": "127.0.0.1"so the LLM API is never exposed to the public internet - Encrypt data at rest: Enable PostgreSQL’s built-in data encryption in
postgresql.confto protect your code library at rest - Run on air-gapped networks if needed: All components of this stack work fully offline, so you can deploy it on isolated servers for high-security projects
- Regularly update models: Pull updated versions of your Ollama models monthly to patch security vulnerabilities and improve translation accuracy
Real-World 2026 Use Cases
This tool is built for real enterprise modernization workflows:
- Modernize 10+ year old PHP e-commerce stores to Next.js + Bun backends without exposing customer PII to third-party AI providers
- Migrate internal jQuery admin dashboards to React + TypeScript for regulated financial institutions that can’t use external AI tools
- Translate legacy PHP APIs to Python FastAPI for machine learning integration teams working with sensitive healthcare data
- Preserve and modernize legacy open source codebases that can’t use third-party AI tools due to restrictive licensing terms
Common Pitfalls to Avoid
- Don’t use underpowered hardware: The 16B DeepSeek Coder V2 model requires at least 32GB of RAM for smooth, low-latency inference. For larger codebases, use a machine with 64GB+ RAM or a dedicated GPU with 16GB+ VRAM
- Don’t skip the pattern library upload: Generic translations will miss your team’s custom coding standards and business logic, leading to buggy output that requires more rework than just translating manually
- Don’t disable testing: Always run unit and integration tests on translated code. AI translation can miss edge cases in complex legacy logic, especially for custom business rules
- Don’t use high temperature settings: Keep the LLM temperature at 0.1 or lower for code translation to reduce hallucinations and inconsistent output
Conclusion
Building a self-hosted, privacy-first legacy code translator gives you full control over your modernization workflow, eliminates compliance risks, and delivers translation accuracy that improves over time as the system learns your codebase’s unique patterns. With the stack outlined in this guide, you can modernize PHP and jQuery codebases of any size without ever sending sensitive code to third-party servers.
Ready to extend the tool? Add features like batch translation for entire repositories, CI/CD integration to auto-translate legacy code as you refactor, or support for additional legacy languages like Classic ASP or Perl. The only limit is your infrastructure.
Enjoyed this article?
Read More Articles