/how-to-build-replit

How to Build a Job board with Replit

Learn how to build a fully functional job board using Replit. Follow our step-by-step guide to create, customize, and launch your own job platform.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

How to Build a Job board with Replit

To build a job board on Replit, you’ll use a simple Node.js + Express backend with a lightweight frontend (HTML + CSS + JS). You’ll store job listings in memory first (for testing), then connect to Replit’s built-in SQLite database for persistence. Replit will host both the server and frontend together so you can run and preview it instantly. The backend handles job creation and listing retrieval through API routes, while the frontend displays jobs and allows creating new ones via form submission. This approach works well in Replit’s always-on environment and can later scale with external databases or Replit Deployments.

 

1. Set up the Replit project

 

From Replit’s dashboard, create a new Repl using the “Node.js” template. This will automatically generate an index.js file (the entry file) and a package.json file (where dependencies go).

  • Click the “+” icon in the left sidebar and create a new folder named public.
  • Inside public, create index.html and script.js.
  • In the root folder, create a new file named database.db (this will be managed automatically by SQLite once you run the app).

 

2. Install dependencies

 

In the Replit shell pane at the bottom, run this command:

npm install express better-sqlite3

 

This sets up Express for your backend server and Better-SQLite3 for local database management (works smoothly on Replit without extra setup).

 

3. Build the backend (index.js)

 

Replace the content of index.js with this real working code:

// Load required packages
const express = require('express');
const Database = require('better-sqlite3');
const app = express();
const db = new Database('database.db');

// Middleware to handle JSON bodies
app.use(express.json());

// Serve static frontend files from the 'public' folder
app.use(express.static('public'));

// Create a jobs table if it doesn't exist
db.prepare(`
  CREATE TABLE IF NOT EXISTS jobs (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT,
    company TEXT,
    description TEXT
  )
`).run();

// Route to get all jobs
app.get('/api/jobs', (req, res) => {
  const jobs = db.prepare('SELECT * FROM jobs').all();
  res.json(jobs);
});

// Route to add a new job
app.post('/api/jobs', (req, res) => {
  const { title, company, description } = req.body;
  if (!title || !company) {
    return res.status(400).json({ error: 'Title and company are required' });
  }
  const stmt = db.prepare('INSERT INTO jobs (title, company, description) VALUES (?, ?, ?)');
  const result = stmt.run(title, company, description);
  res.json({ id: result.lastInsertRowid });
});

// Start server on Replit’s dynamic port
const port = process.env.PORT || 3000;
app.listen(port, () => console.log(`Server running on port ${port}`));

 

This sets up your backend API. When you click “Run” in Replit, you should see “Server running on port…” in the console. You can now open the browser view (the web icon) and it will load your frontend from public/index.html.

 

4. Build the frontend

 

Edit public/index.html with this minimal form and display section:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Replit Job Board</title>
    <style>
      body { font-family: Arial; padding: 20px; }
      form { margin-bottom: 20px; }
      input, textarea { display: block; margin: 5px 0; width: 300px; }
      .job { border: 1px solid #ccc; padding: 10px; margin-bottom: 10px; }
    </style>
  </head>
  <body>
    <h3>Post a Job</h3>
    <form id="jobForm">
      <input type="text" id="title" placeholder="Job Title" required />
      <input type="text" id="company" placeholder="Company Name" required />
      <textarea id="description" placeholder="Description"></textarea>
      <button type="submit">Add Job</button>
    </form>

    <h3>Available Jobs</h3>
    <div id="jobs"></div>

    <script src="script.js"></script>
  </body>
</html>

 

5. Connect frontend and backend

 

Edit public/script.js to handle form submission and fetch job data:

const form = document.getElementById('jobForm');
const jobsDiv = document.getElementById('jobs');

// Load jobs when page opens
async function loadJobs() {
  const res = await fetch('/api/jobs');
  const jobs = await res.json();
  jobsDiv.innerHTML = jobs.map(j => `
    <div class="job">
      <h4>${j.title}</h4>
      <p><strong>${j.company}</strong></p>
      <p>${j.description || ''}</p>
    </div>
  `).join('');
}

// Handle new job submission
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const title = document.getElementById('title').value;
  const company = document.getElementById('company').value;
  const description = document.getElementById('description').value;

  await fetch('/api/jobs', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, company, description })
  });

  form.reset();
  loadJobs();
});

// Initial load
loadJobs();

 

6. How this works in Replit

 

  • When you hit Run, Replit executes index.js and hosts everything under your Replit URL.
  • All static files inside public are automatically served to the browser.
  • The Node.js API runs at the same URL (e.g., https://jobboard-yourusername.replit.app/api/jobs).
  • The SQLite database file (database.db) persists in your Repl, so your data stays even after you stop and restart (unless you delete it manually).

 

7. Common Replit tips and pitfalls

 

  • Secrets: If later you connect to an external database or API, use Replit’s “Secrets” tab (lock icon in left sidebar) to store credentials safely. Access them in Node via process.env.SECRET\_NAME.
  • Git integration: You can link your Repl to GitHub in the left sidebar if you want version control. Always commit package.json and public files, but skip database.db to avoid conflicts.
  • Multiplayer collaboration: You can invite teammates using the “Invite” button — Replit handles live collaboration automatically.
  • Cleanup & performance: Keep large dependencies minimal. Replit has limited RAM, so lightweight libraries like Express and Better-SQLite3 are ideal.

 

This setup gives you a functioning, real job board app all inside Replit — with working backend, database, and frontend logic. You can now expand it by adding search filters, categories, or even authentication later using the same structure.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us

How to Build a Simple Job Board API with Express and Replit Database


<script type="module">
import express from "express";
import Database from "@replit/database";

const app = express();
const db = new Database();

app.use(express.json());

// normalize strings for consistent search keys
const normalize = (str) => str.toLowerCase().trim();

// Create or update a job posting
app.post("/api/jobs", async (req, res) => {
  const { id, title, company, description, tags = [] } = req.body;
  if (!title || !company) return res.status(400).json({ error: "Missing fields" });

  const jobId = id || crypto.randomUUID();
  const job = { id: jobId, title, company, description, tags, createdAt: Date.now() };

  await db.set(`job:${jobId}`, job);
  // store search index by normalized tag string for quick filtering
  for (const tag of tags.map(normalize)) {
    const existing = (await db.get(`tag:${tag}`)) || [];
    if (!existing.includes(jobId)) existing.push(jobId);
    await db.set(`tag:${tag}`, existing);
  }

  res.status(200).json(job);
});

// Search jobs by tag
app.get("/api/jobs/search", async (req, res) => {
  const tag = normalize(req.query.tag || "");
  if (!tag) return res.status(400).json({ error: "Tag required" });

  const jobIds = (await db.get(`tag:${tag}`)) || [];
  const jobs = await Promise.all(jobIds.map((id) => db.get(`job:${id}`)));
  res.json(jobs.filter(Boolean));
});

app.listen(3000, () => console.log("Job board API running on port 3000"));
</script>

How to Enrich Job Postings with Company Data in Your Replit Job Board


<script type="module">
import express from "express";
import fetch from "node-fetch";
import Database from "@replit/database";

const app = express();
const db = new Database();
app.use(express.json());

// Automatically enrich job postings with company data from an external API (e.g. Clearbit)
app.post("/api/enrich-job", async (req, res) => {
  const { title, company, description } = req.body;
  if (!company) return res.status(400).json({ error: "Company field is required" });

  try {
    const apiKey = process.env.CLEARBIT\_KEY;
    const response = await fetch(`https://company.clearbit.com/v2/companies/find?domain=${company}`, {
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    const companyData = response.ok ? await response.json() : {};
    const jobId = crypto.randomUUID();
    const job = {
      id: jobId,
      title,
      company,
      description,
      companyDomain: companyData.domain || company,
      companyLogo: companyData.logo,
      companyLocation: companyData.location,
      createdAt: Date.now(),
    };

    await db.set(`job:${jobId}`, job);
    res.status(200).json(job);
  } catch (err) {
    console.error("Error enriching job:", err);
    res.status(500).json({ error: "Failed to enrich job data" });
  }
});

app.listen(3000, () => console.log("Job board enrichment API running on port 3000"));
</script>

How to Add Secure Edit Tokens to Your Job Board API


<script type="module">
import express from "express";
import Database from "@replit/database";
import crypto from "crypto";

const app = express();
const db = new Database();
app.use(express.json());

// Secure short-lived token for editing job posts
const TOKEN_TTL_MS = 5 _ 60 _ 1000; // 5 minutes

// Generate token for a specific jobId
app.post("/api/jobs/:id/token", async (req, res) => {
  const { id } = req.params;
  const job = await db.get(`job:${id}`);
  if (!job) return res.status(404).json({ error: "Job not found" });

  const token = crypto.randomBytes(16).toString("hex");
  const expiry = Date.now() + TOKEN_TTL_MS;

  await db.set(`editToken:${token}`, { jobId: id, expiry });
  res.json({ token, expiresIn: TOKEN_TTL_MS / 1000 });
});

// Middleware to validate token
async function validateToken(req, res, next) {
  const token = req.headers["x-edit-token"];
  if (!token) return res.status(401).json({ error: "Missing token" });
  const record = await db.get(`editToken:${token}`);
  if (!record || record.expiry < Date.now())
    return res.status(401).json({ error: "Invalid or expired token" });

  req.jobId = record.jobId;
  next();
}

// Update job only if token is valid
app.put("/api/jobs/secure-edit", validateToken, async (req, res) => {
  const { title, description } = req.body;
  const job = await db.get(`job:${req.jobId}`);
  if (!job) return res.status(404).json({ error: "Job not found" });

  const updated = { ...job, title: title || job.title, description: description || job.description, updatedAt: Date.now() };
  await db.set(`job:${req.jobId}`, updated);
  res.json(updated);
});

app.listen(3000, () => console.log("Job board token-secure API running on port 3000"));
</script>

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Contact Us
Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Best Practices for Building a Job board with Replit

To build a job board on Replit that actually works in real-world conditions — not just a prototype — use a Node.js + Express backend with a simple frontend (HTML/CSS/JS or React). Store job listings in a hosted database (like Replit’s built-in SQLite or an external PostgreSQL instance). The key best practices are: keep secrets (API keys, DB credentials) in the Secrets tab, organize code into clear folders, use environment variables, and use the Replit “Run” button smartly (don’t overload the main entrypoint). Local-style assumptions like file watchers or persistent local databases can bite you — Replit resets temporary files after restarts, so always use a persistent DB.

 

Project Setup

 

Create a new Repl using the Node.js template. This gives you a starting point with index.js.

  • Create a folder routes/ to keep your route logic organized.
  • Create another folder models/ if you plan to manage database code or schema separately.
  • Use Replit’s built-in Packages tab to install dependencies like express and sqlite3.

 

npm install express sqlite3

 

Server Basics

 

Inside your index.js file, set up your Express server. This is the main entry point when you click “Run”.

 

// index.js
const express = require('express')
const app = express()
const PORT = process.env.PORT || 3000

app.use(express.json())

// Import routes
const jobsRouter = require('./routes/jobs')
app.use('/api/jobs', jobsRouter)

// Serve static files (for frontend)
app.use(express.static('public'))

app.listen(PORT, () => console.log(`Server running on port ${PORT}`))

 

Jobs Route

 

Create a new file routes/jobs.js. This will handle job-related endpoints (posting and listing jobs).

 

// routes/jobs.js
const express = require('express')
const Router = express.Router()
const db = require('../models/db')

// List all jobs
Router.get('/', (req, res) => {
  db.all('SELECT * FROM jobs', [], (err, rows) => {
    if (err) return res.status(500).json({ error: err.message })
    res.json(rows)
  })
})

// Add a new job
Router.post('/', (req, res) => {
  const { title, company, description } = req.body
  if (!title || !company) return res.status(400).json({ error: 'Missing fields' })

  db.run('INSERT INTO jobs (title, company, description) VALUES (?, ?, ?)', [title, company, description], (err) => {
    if (err) return res.status(500).json({ error: err.message })
    res.status(201).json({ message: 'Job added successfully' })
  })
})

module.exports = Router

 

Database Setup

 

Inside models/db.js, initialize SQLite (persistent in Replit). The database file is saved in your Repl and will persist unless deleted.

 

// models/db.js
const sqlite3 = require('sqlite3').verbose()
const db = new sqlite3.Database('jobs.db')

// Create table if it doesn't exist
db.run('CREATE TABLE IF NOT EXISTS jobs (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, company TEXT, description TEXT)')

module.exports = db

 

Frontend

 

Create a public/ folder with index.html inside. Express’ static middleware will automatically serve it. Here’s a simple frontend to list and add jobs:

 

<!-- public/index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Job Board</title>
  <meta charset="UTF-8" />
  <script>
    async function loadJobs() {
      const res = await fetch('/api/jobs')
      const jobs = await res.json()
      document.getElementById('jobs').innerHTML = jobs.map(j => `<li><b>${j.title}</b> at ${j.company}</li>`).join('')
    }

    async function addJob() {
      const title = document.getElementById('title').value
      const company = document.getElementById('company').value
      const description = document.getElementById('description').value

      await fetch('/api/jobs', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ title, company, description })
      })
      loadJobs()
    }

    window.onload = loadJobs
  </script>
</head>
<body>
  <h1>Job Board</h1>
  <input id="title" placeholder="Job title" />
  <input id="company" placeholder="Company name" />
  <input id="description" placeholder="Description" />
  <button onclick="addJob()">Add Job</button>
  <ul id="jobs"></ul>
</body>
</html>

 

Using Secrets and Environment Variables

 

When you connect to external services (e.g., PostgreSQL, SendGrid API), store credentials in Replit’s Secrets tab (lock icon in sidebar). In your code, access them like:

 

const dbPassword = process.env.DB_PASSWORD

 

Collaboration and Deployment Tips

 

  • Multiplayer: Use the “Invite” button to collaborate in real-time — changes are instant for all users.
  • GitHub Integration: Connect Replit to GitHub for version control. Commit from Replit’s Git sidebar for backups.
  • Deployments: Replit automatically serves your app once started. For public-facing stability, use “Always On” if you have Replit Core.
  • Debugging: Use the Shell tab to run commands manually (e.g., node index.js or sqlite3 jobs.db).
  • Avoid local-style assumptions: Temporary files (anything not in your Repl tree) can disappear — persist all data in databases or Replit files.

 

Common Pitfalls

 

  • Auto-reboot loops: Don’t use nodemon; Replit restarts your server automatically.
  • Port binding: Only use process.env.PORT, don’t hardcode ports.
  • Data loss: In-memory databases reset on restart — always use file-backed or external storage.
  • Secrets exposure: Never hardcode API keys in code; the Repl is shareable by URL.

 

Follow this folder pattern and security setup, and your job board on Replit will be clean, persistent, shareable, and scalable for small production use or MVP testing. The key is treating Replit like a lightweight cloud dev environment — not as a local machine replica.

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022