Learn how to build a powerful blog backend using Replit. Follow step-by-step guidance to create, deploy, and manage your blog with ease.

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
To build a simple yet real blog backend on Replit, create a Node.js + Express project, set up routes for posts, and connect it to a persistent database like Replit’s built-in SQLite. Replit automatically gives you a web URL for your Express app, so you can test API endpoints instantly. You’ll store secrets (like API keys) in the “Secrets” tab, organize the code into separate files (server, routes, and database logic), and make sure everything restarts cleanly when the server updates.
In Replit’s dashboard, click “+ Create Repl”, select “Node.js” template. Replit will generate index.js by default. This file is where your server code goes.
// index.js
const express = require("express");
const bodyParser = require("body-parser");
const Database = require("@replit/database"); // Simple key-value store built into Replit!
const app = express();
app.use(bodyParser.json());
const db = new Database(); // Connect to Replit DB
// Test route - you can visit this in browser
app.get("/", (req, res) => {
res.send("Blog backend is running!");
});
// Start server
app.listen(3000, () => console.log("Server live on port 3000"));
Click “Run” — Replit boots the server and gives you a URL like https://your-repl-name.username.repl.co. That’s your live backend endpoint.
In your file tree on the left, right-click and select “New Folder” → name it `routes`. Inside it, create a new file called posts.js. This keeps your code modular.
// routes/posts.js
const express = require("express");
const router = express.Router();
const Database = require("@replit/database");
const db = new Database();
// Create a post
router.post("/", async (req, res) => {
const { title, content } = req.body;
if (!title || !content) return res.status(400).send("Missing title or content");
const id = Date.now().toString(); // Unique ID based on timestamp
await db.set(id, { id, title, content });
res.send({ id, title, content });
});
// Get all posts
router.get("/", async (req, res) => {
const keys = await db.list();
const posts = [];
for (let key of keys) {
const post = await db.get(key);
posts.push(post);
}
res.send(posts);
});
// Get single post
router.get("/:id", async (req, res) => {
const post = await db.get(req.params.id);
if (!post) return res.status(404).send("Post not found");
res.send(post);
});
// Delete post
router.delete("/:id", async (req, res) => {
await db.delete(req.params.id);
res.send({ message: "Post deleted" });
});
module.exports = router;
Now open index.js again and import that route file.
// index.js (add this near the top)
const postsRoute = require("./routes/posts");
// Below your existing middleware (after app.use(bodyParser.json()))
app.use("/api/posts", postsRoute);
This means that when you visit /api/posts in the browser or with a tool like Postman, your new routes will respond. For example:
If later you want to connect to an external database (like MongoDB), go to the “Secrets” (🔒 icon on left sidebar) and add your MONGODB\_URI variable instead of writing it directly into the file. Then access it as:
const mongoUri = process.env.MONGODB_URI;
This avoids exposing passwords in your code or public Repls.
Replit projects stay live while the tab is open. For a semi-permanent setup, use Replit’s “Always On” feature (Pro accounts) or connect to an external uptime monitor (like UptimeRobot hitting your main endpoint to keep it alive).
You’ve got a working backend that supports basic CRUD (Create, Read, Delete) operations for blog posts using Express and Replit’s built-in database. The key idea is to organize your server entry point (index.js), define API routes in routes/posts.js, and let Replit’s URL serve as your live endpoint. This is a stable, realistic way to build a blog backend right inside Replit’s free or pro environment — no fake tools, just real working components.
const express = require('express');
const Database = require('@replit/database');
const { v4: uuidv4 } = require('uuid');
const app = express();
const db = new Database();
app.use(express.json());
// Create or update a blog post
app.post('/api/posts', async (req, res) => {
const { id, title, content, author } = req.body;
if (!title || !content || !author) {
return res.status(400).json({ error: 'Missing required fields' });
}
const postId = id || uuidv4();
const timestamp = new Date().toISOString();
const post = { id: postId, title, content, author, updatedAt: timestamp };
await db.set(`post:${postId}`, post);
res.json({ message: 'Post saved', post });
});
// Fetch all posts
app.get('/api/posts', async (req, res) => {
const keys = await db.list('post:');
const posts = await Promise.all(keys.map(k => db.get(k)));
posts.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
res.json(posts);
});
// Fetch a single post
app.get('/api/posts/:id', async (req, res) => {
const post = await db.get(`post:${req.params.id}`);
if (!post) return res.status(404).json({ error: 'Post not found' });
res.json(post);
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running at http://localhost:${PORT}`));
const express = require('express');
const fetch = require('node-fetch');
const bodyParser = require('body-parser');
require('dotenv').config();
const app = express();
app.use(bodyParser.json());
app.post('/api/publish', async (req, res) => {
const { title, content } = req.body;
if (!title || !content) {
return res.status(400).json({ error: 'Title and content required' });
}
try {
const response = await fetch('https://api.hashnode.com/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': process.env.HASHNODE\_TOKEN
},
body: JSON.stringify({
query: \`
mutation CreateStory {
createStory(
input: {
title: "${title}",
contentMarkdown: """${content}"""
tags: [{ \_id: "56744723958ef13879b9549b" }]
}
) {
post { slug publication { domain } }
}
}
\`
})
});
const result = await response.json();
if (result.errors) {
return res.status(500).json({ error: 'Failed to publish', details: result.errors });
}
const postUrl = `${result.data.createStory.post.publication.domain}/${result.data.createStory.post.slug}`;
res.json({ message: 'Post published successfully', url: postUrl });
} catch (err) {
res.status(500).json({ error: 'Server error', details: err.message });
}
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`🚀 Blog backend running on port ${PORT}`));
const express = require('express');
const { v4: uuidv4 } = require('uuid');
const Database = require('@replit/database');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const app = express();
const db = new Database();
// Configure multer for image uploads (for blog cover images)
const upload = multer({
dest: 'uploads/',
limits: { fileSize: 3 _ 1024 _ 1024 }, // limit to 3MB
fileFilter: (req, file, cb) => {
const ext = path.extname(file.originalname).toLowerCase();
if (ext !== '.png' && ext !== '.jpg' && ext !== '.jpeg') {
return cb(new Error('Only images are allowed'));
}
cb(null, true);
}
});
app.use(express.json());
app.use('/uploads', express.static('uploads'));
// Save post metadata + image reference in database
app.post('/api/posts/with-image', upload.single('cover'), async (req, res) => {
const { title, content, author } = req.body;
if (!title || !content || !author) {
return res.status(400).json({ error: 'Missing required fields' });
}
const postId = uuidv4();
const imagePath = req.file ? `/uploads/${req.file.filename}` : null;
const post = {
id: postId,
title,
content,
author,
coverImage: imagePath,
createdAt: new Date().toISOString()
};
await db.set(`post:${postId}`, post);
res.json({ message: 'Post created', post });
});
// Clean up old images when deleting posts
app.delete('/api/posts/:id', async (req, res) => {
const id = req.params.id;
const post = await db.get(`post:${id}`);
if (!post) return res.status(404).json({ error: 'Post not found' });
if (post.coverImage) {
const localPath = path.join(\_\_dirname, post.coverImage);
if (fs.existsSync(localPath)) fs.unlinkSync(localPath);
}
await db.delete(`post:${id}`);
res.json({ message: 'Post deleted' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Blog backend running on http://localhost:${PORT}`));

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
A solid and realistic way to build a blog backend on Replit is to create a small Node.js and Express app that exposes routes (for example /posts to fetch blog posts, /admin to add new posts). You store posts in a simple SQLite database (which fits perfectly in Replit) or in a JSON file if you’re just starting. You’ll use Replit’s built-in “Secrets” system for any API keys or database credentials, and Replit’s “Always On” or external deployment (like using Replit Deployments or hosting elsewhere) if you want the backend reachable 24/7. The best practice is to keep your server code organized: one server.js to start the app, one routes/ folder for API endpoints, and one db.js for your database logic. Avoid expecting Replit’s ephemeral filesystem to keep data safe across restarts — always persist important data in SQLite or an external database.
Create a new Repl: choose the “Node.js” template. Replit will make a default index.js. Rename it to server.js for clarity.
server.js.index.js after renaming if it exists.
npm install express sqlite3 cors
Explanation:
express – web framework for routessqlite3 – lightweight local database engine (perfect for Replit)cors – enables your frontend to call your backend safely
// server.js
const express = require('express')
const cors = require('cors')
const app = express()
const PORT = process.env.PORT || 3000
app.use(cors())
app.use(express.json()) // allows JSON bodies in requests
app.get('/', (req, res) => {
res.send('Blog backend is running!')
})
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
Click the green “Run” button on Replit, then open the Replit webview link. You’ll see “Blog backend is running!”. That’s your basic backend.
Add a new file named db.js in the root of your Repl. This will handle SQLite connections. A database.db file will be created automatically when you first run the app.
// db.js
const sqlite3 = require('sqlite3').verbose()
// Create or open the database
const db = new sqlite3.Database('./database.db', (err) => {
if (err) console.error('Database failed to open', err)
else console.log('Connected to SQLite database')
})
// Create a table for posts if it doesn't exist
db.run(`CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
content TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)`)
module.exports = db
Add a new folder named routes and inside it, create a file called posts.js. You’ll separate your routes — this keeps server.js clean.
// routes/posts.js
const express = require('express')
const router = express.Router()
const db = require('../db')
// GET all posts
router.get('/', (req, res) => {
db.all('SELECT * FROM posts ORDER BY created_at DESC', [], (err, rows) => {
if (err) return res.status(500).json({ error: err.message })
res.json(rows)
})
})
// POST a new blog post
router.post('/', (req, res) => {
const { title, content } = req.body
if (!title || !content) return res.status(400).json({ error: 'Missing fields' })
db.run('INSERT INTO posts (title, content) VALUES (?, ?)', [title, content], function(err) {
if (err) return res.status(500).json({ error: err.message })
res.json({ id: this.lastID })
})
})
module.exports = router
Go back to server.js and mount your routes:
// server.js
const express = require('express')
const cors = require('cors')
const app = express()
const PORT = process.env.PORT || 3000
app.use(cors())
app.use(express.json())
// Import and use the posts router
const postsRouter = require('./routes/posts')
app.use('/posts', postsRouter)
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
For any sensitive data (API keys, database URLs, admin passwords), never hardcode them in files. Go to the Replit sidebar → “Secrets” (lock icon), add new keys (for example ADMIN\_TOKEN), and access them in your code like this:
const adminToken = process.env.ADMIN_TOKEN
database.db or switch to a remote DB (like Supabase).routes/, db.js, and server.js separation helps clarity and easier debugging.
Use your browser or a tool like curl or Postman.
curl -X POST https://<your-repl-name>.<your-username>.repl.co/posts \
-H "Content-Type: application/json" \
-d '{"title": "My first post", "content": "Hello from Replit!"}'
Then open /posts from the Replit Webview to see the stored data. You now have a functional, persistent blog backend built the clean way inside Replit.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.