/how-to-build-replit

How to Build a Productivity app with Replit

Learn how to build a powerful productivity app with Replit. Follow our step-by-step guide to create, test, and launch your app efficiently.

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 Productivity app with Replit

A simple but real-world way to build a Productivity App in Replit is to use a full-stack setup with Node.js for the backend and React for the frontend. You’ll create a small task manager where users can add, mark done, and remove tasks. Replit can host both the frontend and backend in the same Repl, and you can use MongoDB Atlas for a persistent database. You’ll store your database connection string safely using Replit Secrets, and you can push your code to GitHub from within Replit once it’s working.

 

Setup the Repl

 

Create a new Repl with the template “Node.js”. You’ll add React manually so both backend and frontend live together. In the left sidebar, you’ll see a file tree — that’s where we’ll add new files/folders for structure:

  • Create a folder client for the frontend (React app).
  • Keep index.js in the Repl root — that will be your backend entry file.

 

// Inside Replit Shell, install dependencies
npm init -y
npm install express cors mongoose

 

That gives you backend basics: Express for server creation and Mongoose for MongoDB connection.

 

Backend Setup (Node + Express)

 

Edit index.js at root. Here’s your initial working backend code:

 

// index.js
const express = require('express')
const mongoose = require('mongoose')
const cors = require('cors')

// Environment variable secret (set in Replit Secrets)
const MONGO_URI = process.env.MONGO_URI  

const app = express()
app.use(cors())
app.use(express.json())

// Connect to MongoDB
mongoose.connect(MONGO_URI)
  .then(() => console.log('Connected to MongoDB'))
  .catch(err => console.error(err))

// Define schema for tasks
const taskSchema = new mongoose.Schema({
  title: String,
  done: { type: Boolean, default: false }
})

const Task = mongoose.model('Task', taskSchema)

// Route: Get all tasks
app.get('/tasks', async (req, res) => {
  const tasks = await Task.find()
  res.json(tasks)
})

// Route: Add new task
app.post('/tasks', async (req, res) => {
  const newTask = new Task({ title: req.body.title })
  await newTask.save()
  res.json(newTask)
})

// Route: Toggle done
app.put('/tasks/:id', async (req, res) => {
  const updated = await Task.findByIdAndUpdate(req.params.id, { done: req.body.done }, { new: true })
  res.json(updated)
})

// Route: Delete task
app.delete('/tasks/:id', async (req, res) => {
  await Task.findByIdAndDelete(req.params.id)
  res.json({ message: 'Deleted' })
})

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

 

This file runs your backend API. You’ll see output in the Replit console when you click "Run". Before running, you must set a Replit Secret named MONGO\_URI (on left sidebar → "Secrets" icon → add key/value). Use a connection string from MongoDB Atlas.

 

Frontend Setup (React Client inside client/ folder)

 

Now create your React client manually inside client/ folder. In the Shell:

 

npx create-react-app client

 

Once it finishes, Replit adds a nested package.json for the client. You now have two package.json files: one in root (backend) and one in client/ (frontend). You’ll run each separately when testing locally, but Replit can serve your frontend from the same port in production.

In your root folder, add a new script in the backend package.json to build React once:

 

"scripts": {
  "start": "node index.js",
  "build-client": "npm run build --prefix client"
}

 

Inside client/src/, open App.js and replace all its content with this simple connected frontend:

 

// client/src/App.js
import React, { useState, useEffect } from 'react'

const API_URL = 'https://your-repl-url.repl.co'  // Change it to your Replit web URL

function App() {
  const [tasks, setTasks] = useState([])
  const [title, setTitle] = useState('')

  // Fetch all tasks
  useEffect(() => {
    fetch(`${API_URL}/tasks`)
      .then(res => res.json())
      .then(setTasks)
  }, [])

  // Add new task
  const addTask = async () => {
    const res = await fetch(`${API_URL}/tasks`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ title })
    })
    const data = await res.json()
    setTasks([...tasks, data])
    setTitle('')
  }

  // Toggle done
  const toggleDone = async (id, done) => {
    const res = await fetch(`${API_URL}/tasks/${id}`, {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ done })
    })
    const updated = await res.json()
    setTasks(tasks.map(t => (t._id === id ? updated : t)))
  }

  // Delete task
  const deleteTask = async (id) => {
    await fetch(`${API_URL}/tasks/${id}`, { method: 'DELETE' })
    setTasks(tasks.filter(t => t._id !== id))
  }

  return (
    <div style={{ padding: 20 }}>
      <h1>Productivity App</h1>
      <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder="New task" />
      <button onClick={addTask}>Add</button>
      <ul>
        {tasks.map(task => (
          <li key={task._id}>
            <input type="checkbox" checked={task.done} onChange={() => toggleDone(task._id, !task.done)} />
            {task.title}
            <button onClick={() => deleteTask(task._id)}>X</button>
          </li>
        ))}
      </ul>
    </div>
  )
}

export default App

 

Connect Frontend and Backend in Replit

 

You can build the React frontend once and serve static files from the backend so that Replit runs both together. After your React app is ready, run this in the Shell:

 

npm run build-client

 

This creates a client/build folder. Now modify your index.js in the backend to serve that folder before your API routes:

 

// Add this line after express setup but before routes
app.use(express.static('client/build'))

 

This allows Replit to serve your React app and backend together at your Replit URL (like https://your-repl-name.username.repl.co). When you click "Run", the server starts and users can see the React UI hitting the same backend API.

 

Common Pitfalls & Tips

 

  • Secrets are not persisted in code: always use the Replit Secrets panel for sensitive info like MongoDB URLs. Never hardcode them.
  • Multiplayer coding: Replit allows real-time collaboration, so each teammate can code together, but be careful saving Repl Secrets—only the owner can add/edit.
  • Database connectivity: open MongoDB access to any IP (or 0.0.0.0/0) while testing on Replit since its IP changes. For production, use stricter rules.
  • Server restarting: Replit automatically re-runs whenever files change. For heavy projects, consider using command-line “nodemon” if you want better control: npm install nodemon and update your start script.
  • Deployments: For a more stable version, you can export this project to GitHub and deploy via another host later, but for learning and prototypes, Replit hosting works fine.

 

Following this step-by-step structure gives you a complete productivity app directly within Replit — backend with Express, persistent storage via MongoDB Atlas, and a React frontend. It runs fully in the Replit editor, no local system required.

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 Task Management API for Your Productivity App


<!-- 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());

// Task schema stored as JSON per user
const normalizeTask = (task) => ({
  id: task.id || Date.now().toString(),
  title: task.title?.trim() || "Untitled",
  done: !!task.done,
  createdAt: task.createdAt || new Date().toISOString()
});

// Create or update a task
app.post("/api/tasks", async (req, res) => {
  const user = req.query.user || "default";
  const task = normalizeTask(req.body);
  const tasks = (await db.get(user)) || [];
  const updated = [...tasks.filter(t => t.id !== task.id), task];
  await db.set(user, updated);
  res.json({ ok: true, task });
});

// Retrieve all tasks
app.get("/api/tasks", async (req, res) => {
  const user = req.query.user || "default";
  const tasks = (await db.get(user)) || [];
  res.json(tasks.sort((a,b)=>new Date(b.createdAt)-new Date(a.createdAt)));
});

// Toggle task done
app.post("/api/tasks/:id/toggle", async (req, res) => {
  const user = req.query.user || "default";
  const id = req.params.id;
  const tasks = (await db.get(user)) || [];
  const updated = tasks.map(t => t.id === id ? { ...t, done: !t.done } : t);
  await db.set(user, updated);
  res.json({ ok: true });
});

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

How to Sync Completed Tasks to Notion in Your Replit Productivity App


<!-- server.js: Sync completed tasks to external Notion workspace using secret API key -->
<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());

app.post("/api/syncCompletedTasks", async (req, res) => {
  const NOTION_API_KEY = process.env.NOTION_API_KEY;
  const NOTION_DATABASE_ID = process.env.NOTION_DATABASE_ID;
  if (!NOTION_API_KEY || !NOTION_DATABASE_ID)
    return res.status(400).json({ error: "Missing Notion configuration" });

  const { user = "default" } = req.query;
  const tasks = (await db.get(user)) || [];
  const completed = tasks.filter(t => t.done && !t.synced);

  const results = [];
  for (const task of completed) {
    const response = await fetch("https://api.notion.com/v1/pages", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${NOTION_API_KEY}`,
        "Content-Type": "application/json",
        "Notion-Version": "2022-06-28"
      },
      body: JSON.stringify({
        parent: { database_id: NOTION_DATABASE\_ID },
        properties: {
          Name: { title: [{ text: { content: task.title } }] },
          Status: { select: { name: "Done" } },
          CompletedAt: { date: { start: new Date().toISOString() } }
        }
      })
    });

    if (response.ok) {
      task.synced = true;
      results.push({ id: task.id, status: "synced" });
    } else {
      results.push({ id: task.id, status: "failed" });
    }
  }

  if (results.length) await db.set(user, tasks);

  res.json({ synced: results.length, details: results });
});

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

How to Set Up Automated Email Reminders with Replit’s Database and Cron Jobs


<!-- server.js: Background email reminder queue using Replit's Database and Cron job -->
<script type="module">
import express from "express";
import Database from "@replit/database";
import fetch from "node-fetch";

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

app.use(express.json());

// Add task with reminder time
app.post("/api/taskWithReminder", async (req, res) => {
  const { title, remindAt, email } = req.body;
  if (!title || !remindAt || !email)
    return res.status(400).json({ error: "Missing required fields" });

  const task = {
    id: Date.now().toString(),
    title,
    remindAt,
    email,
    notified: false
  };

  const tasks = (await db.get("reminders")) || [];
  tasks.push(task);
  await db.set("reminders", tasks);
  res.json({ ok: true, task });
});

// Cron-safe background reminder check
app.get("/cron/sendReminders", async (req, res) => {
  const tasks = (await db.get("reminders")) || [];
  const now = Date.now();
  const pending = tasks.filter(t => !t.notified && new Date(t.remindAt).getTime() <= now);
  
  const results = [];
  for (const task of pending) {
    try {
      // Example fake email API call
      const r = await fetch("https://api.example-mailer.com/send", {
        method: "POST",
        headers: { "Content-Type": "application/json", "x-api-key": process.env.MAIL_API_KEY },
        body: JSON.stringify({
          to: task.email,
          subject: `Reminder: ${task.title}`,
          text: `It's time for "${task.title}".`
        })
      });
      if (r.ok) {
        task.notified = true;
        results.push({ id: task.id, status: "sent" });
      } else {
        results.push({ id: task.id, status: "failed" });
      }
    } catch {
      results.push({ id: task.id, status: "error" });
    }
  }

  await db.set("reminders", tasks);
  res.json({ sent: results.length, details: results });
});

app.listen(3000, () => console.log("✅ Reminder 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 Productivity app with Replit

A productivity app built on Replit works best when you treat Replit like a real, cloud-hosted workspace — not just an online code sandbox. The key is to separate front-end (React or simple HTML/JS), back-end (Node.js or Python Flask/FastAPI), and persistent storage (Replit Database, SQLite, or an external DB). Keep your secrets in Replit’s Secrets tab, not inside code. Use replit.yaml for custom run commands, commit meaningful changes with Git, and watch out for Replit’s limits: the environment sleeps if inactive (unless you have Always On), and file writes go to ephemeral storage in some templates. Build modularly — one Repl per purpose (API vs client), then connect them using the web URLs Replit provides.

 

Setting Up Your Project Structure

 

Create a new Node.js Repl named productivity-app. Inside, create:

  • server.js at root — handles your backend API
  • public/ folder — holds your frontend files (HTML, CSS, JS)
  • database.js — handles data logic (using Replit Database or external DB)

 

// In the Replit shell:
mkdir public
touch server.js database.js public/index.html public/script.js public/style.css

 

Backend (Node.js + Express)

 

Go to server.js and build a small Express server that serves your frontend and exposes simple productivity APIs (for tasks, notes, etc):

 

// server.js
import express from "express";
import { db } from "./database.js"; // import our database module
const app = express();

app.use(express.json());
app.use(express.static("public")); // serve files from /public

// Example API: Add a task
app.post("/api/tasks", async (req, res) => {
  const { text } = req.body;
  if (!text) return res.status(400).json({ error: "Task text required" });

  const id = Date.now().toString();
  await db.set(id, { text, done: false });
  res.json({ id, text, done: false });
});

// Example API: Get all tasks
app.get("/api/tasks", async (req, res) => {
  const keys = await db.list();
  const tasks = [];
  for (const key of keys) {
    tasks.push(await db.get(key));
  }
  res.json(tasks);
});

app.listen(3000, () => console.log("Server running on port 3000"));

 

Explanation: this runs your web server inside Replit. The express.static line makes Replit serve whatever’s inside your public folder automatically at the root URL. The other routes handle simple APIs. The port 3000 is correct for Replit because it auto-detects that port for web previews.

 

Database Connection

 

Replit provides a built-in lightweight key-value store called @replit/database. Install it using the Replit Packages tool or run this in Replit shell:

 

npm install @replit/database

 

Inside database.js add:

 

// database.js
import Database from "@replit/database";
export const db = new Database(); // default uses Replit's auto-connected DB

 

This requires no setup — the data lives in your Replit account (persistent across runs). For real production-like storage, later you can switch to external databases (Postgres, MongoDB, etc.) using connection strings stored in Replit Secrets.

 

Frontend (Public Folder)

 

Inside public/index.html, put the minimal HTML skeleton that connects to your backend APIs:

 

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>My Productivity App</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <h1>Tasks</h1>
  <form id="task-form">
    <input id="task-input" placeholder="New Task..." />
    <button>Add</button>
  </form>
  <ul id="task-list"></ul>

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

 

Then in public/script.js add this code to call your backend APIs:

 

// public/script.js
const form = document.getElementById("task-form");
const input = document.getElementById("task-input");
const list = document.getElementById("task-list");

// Load existing tasks on page load
window.addEventListener("load", async () => {
  const res = await fetch("/api/tasks");
  const tasks = await res.json();
  for (const t of tasks) addTaskToDOM(t);
});

form.addEventListener("submit", async (e) => {
  e.preventDefault();
  const text = input.value.trim();
  if (!text) return;

  const res = await fetch("/api/tasks", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ text })
  });
  const task = await res.json();
  addTaskToDOM(task);
  input.value = "";
});

function addTaskToDOM(task) {
  const li = document.createElement("li");
  li.textContent = task.text;
  list.appendChild(li);
}

 

That’s enough to create and list tasks dynamically from the server. When you run the Repl, Replit will show a URL like https://productivity-app..repl.co — that’s your working app URL.

 

Using Secrets and Environment Variables

 

If you connect external APIs (e.g. Google Calendar or OpenAI), never paste tokens in code. In Replit left sidebar, open Tools → Secrets and add them there (e.g. API\_KEY). You can then access them safely in code like this:

 

const apiKey = process.env.API_KEY; // available from Replit Secrets

 

Deployment and Collaboration Tips

 

  • Link to GitHub via “Version Control” → Connect to keep backup and revision history.
  • Use multiplayer when collaborating — each user’s cursor appears live, but only give edit rights to trusted teammates.
  • Customize run command in replit.yaml if needed:
    run: npm start
      
    Then in package.json set:
    "scripts": { "start": "node server.js" }
      
  • Enable Always On if you’re on a Hacker plan so your app doesn’t sleep. Otherwise, it stops when idle.

 

Common Pitfalls and Best Practices

 

  • Don’t write secrets in code. Use Replit Secrets always.
  • Test APIs inside Replit Shell with curl before wiring the frontend — helps you isolate issues faster.
  • Avoid huge npm installs. Replit storage is limited; keep dependencies minimal.
  • Replit Database is key-value only; for structured queries use external Postgres.
  • Push your code often — Replit autosaves but Git commits give you safety net.

 

By following this structure — backend in server.js, frontend static files in public/, and environment-safe secrets setup — you’ll have a solid, maintainable productivity app running entirely inside Replit that behaves closely to a small-scale production app.

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