Learn how to build a social media feed using Replit. Follow easy steps to code, connect APIs, and create a dynamic, shareable social platform.

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 simple and realistic way to build a social media feed on Replit is to create a small full‑stack app using Node.js (Express) for the backend and HTML, CSS, JavaScript for the frontend. You’ll store posts in a small JSON file or a hosted database like Replit’s Database. The backend will expose routes for posting and fetching posts, and the frontend will call those routes with fetch() to display the feed. This setup works entirely inside one Replit project, can be collaboratively edited, and deployed using Replit’s built‑in web server environment.
In Replit, create a new Repl using the “Node.js” template. Your folder structure will look like this:
npm install express body-parser
// index.js
const express = require("express")
const fs = require("fs")
const bodyParser = require("body-parser")
const app = express()
const PORT = 3000
// Middleware to parse JSON
app.use(bodyParser.json())
// Serve static files from public folder
app.use(express.static("public"))
// Helper to read and write posts from posts.json
function getPosts() {
try {
return JSON.parse(fs.readFileSync("posts.json", "utf8"))
} catch {
return []
}
}
function savePosts(posts) {
fs.writeFileSync("posts.json", JSON.stringify(posts, null, 2))
}
// Route to get all posts
app.get("/api/posts", (req, res) => {
const posts = getPosts()
res.json(posts)
})
// Route to add new post
app.post("/api/posts", (req, res) => {
const posts = getPosts()
const newPost = {
id: Date.now(),
text: req.body.text || "",
createdAt: new Date().toISOString()
}
posts.unshift(newPost) // add to top of feed
savePosts(posts)
res.json(newPost)
})
app.listen(PORT, () => console.log(`Server running on port ${PORT}`))
✅ Save this file in the root of your project. This will be your main web server file in Replit, which automatically runs when you press the “Run” button.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Mini Social Feed</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>My Social Feed</h1>
<form id="postForm">
<input id="postText" type="text" placeholder="What's on your mind?" required />
<button type="submit">Post</button>
</form>
<div id="feed"></div>
<script src="script.js"></script>
</body>
</html>
This page has a simple form for submitting posts and a container (feed) for showing them.
// public/script.js
async function loadFeed() {
const response = await fetch("/api/posts")
const posts = await response.json()
const feedDiv = document.getElementById("feed")
feedDiv.innerHTML = "" // clear old content
posts.forEach(post => {
const div = document.createElement("div")
div.className = "post"
div.innerHTML = `<p>${post.text}</p><small>${new Date(post.createdAt).toLocaleString()}</small>`
feedDiv.appendChild(div)
})
}
document.getElementById("postForm").addEventListener("submit", async (e) => {
e.preventDefault()
const textInput = document.getElementById("postText")
const newPost = { text: textInput.value }
await fetch("/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newPost)
})
textInput.value = ""
loadFeed() // refresh after posting
})
// Load feed on page load
loadFeed()
This script uses the browser’s fetch API to call your server’s endpoints. When the user submits a new post, it’s stored on the backend and instantly refreshed on screen.
body {
font-family: Arial, sans-serif;
max-width: 600px;
margin: 2em auto;
background: #f4f4f4;
padding: 1em;
}
.post {
background: white;
padding: 1em;
margin-bottom: 1em;
border-radius: 5px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
[]
This starts as an empty array; the server will fill it up with your posts.
Click “Run” in Replit. The console should show “Server running on port 3000.” Then open the preview (or click the webview icon). Type a message and post — you’ll see your feed update instantly. The data persists as long as your Repl is alive.
// If you prefer Replit Database:
// 1. Install: npm install @replit/database
const Database = require("@replit/database")
const db = new Database()
// Replace getPosts and savePosts:
async function getPosts() {
return (await db.get("posts")) || []
}
async function savePosts(posts) {
await db.set("posts", posts)
}
This way the data stays even when the container resets, and no manual file writes are needed.
That’s a complete and working social feed foundation inside Replit: one Express backend, one static frontend, data persistence via JSON or Replit DB, and instant live preview.
<!-- server.js -->
<script type="module">
import express from "express";
import { Database } from "@replit/database";
const db = new Database();
const app = express();
app.use(express.json());
// Structure posts in a feed collection
// Each post: { id, author, content, createdAt, likes }
app.post("/api/posts", async (req, res) => {
const { author, content } = req.body;
if (!author || !content) return res.status(400).json({ error: "Missing fields" });
const id = Date.now().toString();
const post = { id, author, content, createdAt: new Date().toISOString(), likes: 0 };
await db.set(id, post);
res.status(201).json(post);
});
// Pagination + ordering newest first
app.get("/api/feed", async (req, res) => {
const allPosts = await db.list();
const posts = await Promise.all(allPosts.map(async key => await db.get(key)));
posts.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
const page = parseInt(req.query.page || "1");
const limit = 5;
const start = (page - 1) \* limit;
const end = start + limit;
res.json({ page, posts: posts.slice(start, end) });
});
app.post("/api/posts/:id/like", async (req, res) => {
const post = await db.get(req.params.id);
if (!post) return res.status(404).json({ error: "Not found" });
post.likes += 1;
await db.set(req.params.id, post);
res.json(post);
});
app.listen(3000, () => console.log("Server running on port 3000"));
</script>
<!-- server.js -->
<script type="module">
import express from "express";
import fetch from "node-fetch";
import { config } from "dotenv";
config();
const app = express();
app.use(express.json());
// Example: fetching user profile pictures from an external avatar API (e.g., DiceBear)
app.get("/api/users/:username/avatar", async (req, res) => {
const { username } = req.params;
try {
const response = await fetch(`https://api.dicebear.com/7.x/thumbs/svg?seed=${encodeURIComponent(username)}`);
if (!response.ok) throw new Error("Avatar service failed");
const svg = await response.text();
res.setHeader("Content-Type", "image/svg+xml");
res.send(svg);
} catch (err) {
res.status(500).json({ error: "Failed to fetch avatar" });
}
});
// Example: posting a new feed item that auto-includes an external avatar and summary
app.post("/api/feed", async (req, res) => {
const { username, text } = req.body;
if (!username || !text) return res.status(400).json({ error: "Missing fields" });
try {
const avatarResponse = await fetch(`https://api.dicebear.com/7.x/thumbs/svg?seed=${encodeURIComponent(username)}`);
const avatarSvg = await avatarResponse.text();
const post = {
id: Date.now().toString(),
author: username,
content: text,
avatar: `data:image/svg+xml;base64,${Buffer.from(avatarSvg).toString("base64")}`,
createdAt: new Date().toISOString(),
};
// For real usage, save post in @replit/database, MongoDB Atlas, etc.
res.status(201).json(post);
} catch (err) {
res.status(500).json({ error: "Failed to create post" });
}
});
app.listen(3000, () => console.log("Server running on port 3000"));
</script>
<!-- server.js -->
<script type="module">
import express from "express";
import { Database } from "@replit/database";
const app = express();
const db = new Database();
app.use(express.json());
// Route to fetch feed items with optimistic UI-friendly timestamps and caching
app.get("/api/feed", async (req, res) => {
try {
const cursor = req.query.after ? parseInt(req.query.after) : Date.now();
const allKeys = await db.list();
const posts = await Promise.all(allKeys.map(k => db.get(k)));
const sorted = posts
.filter(p => !!p)
.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt))
.filter(p => new Date(p.createdAt).getTime() < cursor)
.slice(0, 5);
const nextCursor = sorted.length ? new Date(sorted[sorted.length - 1].createdAt).getTime() : null;
res.json({
posts: sorted,
nextCursor,
fetchedAt: new Date().toISOString()
});
} catch {
res.status(500).json({ error: "Feed fetch failed" });
}
});
// New post that triggers cache invalidation key in db for front-end ETag
app.post("/api/posts", async (req, res) => {
const { author, content } = req.body;
if (!author || !content) return res.status(400).json({ error: "Missing fields" });
const id = Date.now().toString();
const post = { id, author, content, createdAt: new Date().toISOString(), likes: 0 };
await db.set(id, post);
const cacheToken = Math.random().toString(36).slice(2);
await db.set("feed_cache_token", cacheToken);
res.status(201).json({ post, cacheToken });
});
app.get("/api/feed/cache-token", async (req, res) => {
const token = await db.get("feed_cache_token");
res.json({ token });
});
app.listen(3000, () => console.log("Feed service running on port 3000"));
</script>

Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
Building a social media feed on Replit works best if you separate your logic clearly: a backend API (Node.js with Express) that handles posts, users, and storage (for example, MongoDB Atlas), and a frontend (React or basic HTML/JS) that fetches and displays that data. You must rely on environment variables for secrets (like MongoDB credentials), use Replit’s built-in web server to serve your API, and keep files structured so you don’t break Replit’s automatic runner. Replit can easily host small-to-medium feeds if you manage data well and avoid long-running background jobs.
Create a Node.js Repl and organize it like this:
/ (root)
├── index.js // Your entry file - Replit runs this automatically
├── routes/
│ └── posts.js // All endpoints for creating/fetching posts
├── models/
│ └── Post.js // Mongoose (MongoDB) schema
├── public/
│ └── index.html // Simple feed display
├── .replit // Replit configuration file
└── package.json
Use MongoDB Atlas—it’s compatible with Replit and free for small projects. In Replit:
This file starts your Express server, connects to MongoDB, and includes your routes.
import express from "express"
import mongoose from "mongoose"
import postsRouter from "./routes/posts.js"
const app = express()
// Middleware to parse JSON request bodies
app.use(express.json())
// Connect to MongoDB using secret from Replit
mongoose.connect(process.env.MONGO_URI, {
useNewUrlParser: true,
useUnifiedTopology: true
}).then(() => console.log("Connected to MongoDB"))
.catch(err => console.error(err))
// Include routes
app.use("/api/posts", postsRouter)
// Serve static frontend from /public
app.use(express.static("public"))
// Start server
app.listen(3000, () => console.log("Server running on port 3000"))
import mongoose from "mongoose"
const postSchema = new mongoose.Schema({
author: String,
content: String,
createdAt: { type: Date, default: Date.now }
})
export default mongoose.model("Post", postSchema)
This creates two endpoints – one to list posts, another to add a new post.
import express from "express"
import Post from "../models/Post.js"
const router = express.Router()
// Get all posts
router.get("/", async (req, res) => {
const posts = await Post.find().sort({ createdAt: -1 })
res.json(posts)
})
// Add a post
router.post("/", async (req, res) => {
const { author, content } = req.body
const newPost = new Post({ author, content })
await newPost.save()
res.json(newPost)
})
export default router
This is a simple HTML/JS feed that interacts with your API.
<!DOCTYPE html>
<html>
<head>
<title>My Social Feed</title>
<style>
body { font-family: Arial; margin: 20px; }
.post { margin-bottom: 15px; padding: 10px; border: 1px solid #ddd; }
</style>
</head>
<body>
<h2>My Social Feed</h2>
<form id="post-form">
<input id="author" placeholder="Your name" required />
<textarea id="content" placeholder="Write something..." required></textarea>
<button type="submit">Post</button>
</form>
<div id="feed"></div>
<script>
async function loadPosts() {
const res = await fetch("/api/posts")
const posts = await res.json()
const feed = document.getElementById("feed")
feed.innerHTML = ""
posts.forEach(p => {
const div = document.createElement("div")
div.className = "post"
div.textContent = `${p.author}: ${p.content}`
feed.appendChild(div)
})
}
document.getElementById("post-form").addEventListener("submit", async (e) => {
e.preventDefault()
const author = document.getElementById("author").value
const content = document.getElementById("content").value
await fetch("/api/posts", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({ author, content })
})
e.target.reset()
loadPosts()
})
loadPosts()
</script>
</body>
</html>
process.env.<NAME>.npm start or node index.js in the .replit file. Example: run = "npm start".
After running your Repl, you’ll have a functioning social feed: posts are stored in MongoDB, retrieved dynamically, and the frontend updates instantly after each post. For larger scale or authentication features, you can expand with JWT or Replit Auth, but this structure is the clean, safe, and “Replit-friendly” foundation for your social feed.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.