/how-to-build-replit

How to Build a Event calendar app with Replit

Learn how to build an event calendar app using Replit. Follow this step-by-step guide to create, customize, and deploy your own web app easily.

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 Event calendar app with Replit

The simplest valid and scalable way to build an event calendar app on Replit is to use a Node.js + Express backend with a small HTML/CSS/JS frontend. The backend will store events (you can start with in-memory storage, then move to a Replit Database). The frontend will display and allow adding events using simple forms. Replit makes this easy to run in one environment — your frontend can live in the same Repl as your backend, and you can deploy it directly using the “Run” button.

 

Step 1: Create a new Node.js Repl

 

When you create a new Repl, choose the Node.js template. You’ll start with a main file named index.js.

In Replit, all server code (Express, APIs, etc.) goes into index.js by default. All static files (HTML, CSS, JS for the frontend) should live inside a public folder you’ll create manually.

 

Step 2: Install and set up Express

 

Open the Shell tab at the bottom of Replit and run:

npm install express

This installs Express, the lightweight web server library Node apps use. After installation, open your index.js file and replace its content with:

// index.js

const express = require("express")
const app = express()
const port = 3000

// Allow Express to read JSON and form data in requests
app.use(express.json())
app.use(express.urlencoded({ extended: true }))

// Serve static files from the "public" folder
app.use(express.static("public"))

// Temporary in-memory storage for events
let events = []

// GET endpoint to fetch all events
app.get("/api/events", (req, res) => {
  res.json(events)
})

// POST endpoint to add a new event
app.post("/api/events", (req, res) => {
  const { title, date, description } = req.body
  // Basic validation
  if (!title || !date) return res.status(400).send("Missing title or date")
  const newEvent = { id: Date.now(), title, date, description: description || "" }
  events.push(newEvent)
  res.status(201).json(newEvent)
})

app.listen(port, () => {
  console.log(`Server running on http://localhost:${port}`)
})

This code defines an API for your frontend to retrieve and add events. Right now, it only stores events in memory — they reset whenever you restart the Repl. We'll handle persistence in a later step.

 

Step 3: Create the public folder and frontend files

 

In your Replit file explorer, click the + New Folder icon, name it public. Inside it, create these files:

  • index.html — for displaying and submitting events
  • style.css — for styling
  • script.js — for frontend logic to communicate with backend

Paste this inside public/index.html:

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Event Calendar</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <h1>Event Calendar</h1>

  <form id="event-form">
    <input type="text" id="title" placeholder="Event title" required />
    <input type="date" id="date" required />
    <textarea id="description" placeholder="Description"></textarea>
    <button type="submit">Add Event</button>
  </form>

  <div id="events"></div>

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

Then, inside public/style.css, paste this for simple styling:

body {
  font-family: sans-serif;
  margin: 20px;
  background: #f5f5f5;
}

form {
  margin-bottom: 20px;
}

#events {
  background: white;
  border-radius: 5px;
  padding: 10px;
}

.event {
  border-bottom: 1px solid #ccc;
  padding: 5px 0;
}

Now, add logic in public/script.js to fetch and submit events:

// public/script.js

async function loadEvents() {
  const res = await fetch("/api/events")
  const events = await res.json()
  const container = document.getElementById("events")
  container.innerHTML = ""

  events.forEach(ev => {
    const div = document.createElement("div")
    div.classList.add("event")
    div.innerHTML = `<strong>${ev.title}</strong> - ${ev.date}<br/>${ev.description}`
    container.appendChild(div)
  })
}

document.getElementById("event-form").addEventListener("submit", async (e) => {
  e.preventDefault()
  const title = document.getElementById("title").value
  const date = document.getElementById("date").value
  const description = document.getElementById("description").value

  await fetch("/api/events", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ title, date, description })
  })

  document.getElementById("event-form").reset()
  loadEvents()
})

// Load events when page loads
loadEvents()

Now click “Run” in Replit. Your app should open with a form to add events and a list to display them dynamically.

 

Step 4: Add persistent storage (optional but realistic)

 

Replit has a built-in lightweight key-value store called the Replit Database. It’s simple to use for prototypes like this. To use it, install this package in the Shell:

npm install @replit/database

Then edit your index.js — replace the in-memory code for storing events with persistent storage:

// add near top after requires
const Database = require("@replit/database")
const db = new Database()

// Instead of 'let events = []', remove that line

// Update the GET route
app.get("/api/events", async (req, res) => {
  const events = (await db.get("events")) || []
  res.json(events)
})

// Update the POST route
app.post("/api/events", async (req, res) => {
  const { title, date, description } = req.body
  if (!title || !date) return res.status(400).send("Missing title or date")

  const events = (await db.get("events")) || []
  const newEvent = { id: Date.now(), title, date, description: description || "" }
  events.push(newEvent)
  await db.set("events", events)
  res.status(201).json(newEvent)
})

Now, even if you reload or restart, your events remain stored in Replit’s Database. You can verify stored data via the Replit “Database” side panel.

 

Step 5: Common Replit-specific gotchas

 

  • Port binding: Always listen on port 3000; Replit automatically maps it publicly.
  • Don’t use localhost URLs on frontend — just use relative paths (like /api/events), since Replit serves backend and frontend under the same domain.
  • Secrets: If you connect to an external DB later (MongoDB, etc.), put credentials in the Replit “Secrets” tab, not directly in code.
  • Deployment: Replit auto-hosts when you click “Run”. If you want a long-running version, turn on the “Always On” feature (requires a paid plan).

 

At this point

 

You have a working, persistent event calendar web app built on Replit — using Express for backend, a simple frontend served from the same Repl, and persistent storage with Replit DB. You can extend it by adding user authentication, sorting events by date, or migrating to a real calendar UI library later, but the structure here is fully real and deployable within Replit’s environment today.

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 Set Up a Simple Express Server for Your Event Calendar App


<!-- server.js -->
<script type="module">
import express from "express";
import bodyParser from "body-parser";
import { v4 as uuidv4 } from "uuid";

const app = express();
app.use(bodyParser.json());

let events = [
  {
    id: uuidv4(),
    title: "Team Sync",
    date: "2024-10-01",
    startTime: "10:00",
    endTime: "11:00",
    description: "Weekly check-in",
  },
];

app.get("/api/events", (req, res) => {
  const { date } = req.query;
  const filtered = date
    ? events.filter((event) => event.date === date)
    : events;
  res.json(filtered);
});

app.post("/api/events", (req, res) => {
  const { title, date, startTime, endTime, description } = req.body;
  if (!title || !date) return res.status(400).json({ error: "Missing fields" });
  const newEvent = {
    id: uuidv4(),
    title,
    date,
    startTime,
    endTime,
    description,
  };
  events.push(newEvent);
  res.status(201).json(newEvent);
});

app.put("/api/events/:id", (req, res) => {
  const { id } = req.params;
  const idx = events.findIndex((e) => e.id === id);
  if (idx === -1) return res.status(404).json({ error: "Event not found" });
  events[idx] = { ...events[idx], ...req.body };
  res.json(events[idx]);
});

app.delete("/api/events/:id", (req, res) => {
  const { id } = req.params;
  events = events.filter((e) => e.id !== id);
  res.json({ message: "Event deleted", id });
});

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

How to Sync Your Event Calendar with Google Calendar Using Express on Replit


<!-- googleCalendarSync.js -->
<script type="module">
import express from "express";
import { google } from "googleapis";

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

// Use Replit Secrets for these
const CLIENT_ID = process.env.GOOGLE_CLIENT\_ID;
const CLIENT_SECRET = process.env.GOOGLE_CLIENT\_SECRET;
const REDIRECT_URI = process.env.GOOGLE_REDIRECT\_URI; // e.g., https://your-repl-name.username.repl.co/oauth2callback
const REFRESH_TOKEN = process.env.GOOGLE_REFRESH\_TOKEN;

const oAuth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT\_URI);
oAuth2Client.setCredentials({ refresh_token: REFRESH_TOKEN });

const calendar = google.calendar({ version: "v3", auth: oAuth2Client });

// Sync event from your local event store to Google Calendar
app.post("/api/syncToGoogle", async (req, res) => {
  try {
    const { title, description, startTime, endTime } = req.body;

    const event = {
      summary: title,
      description,
      start: { dateTime: new Date(startTime).toISOString(), timeZone: "UTC" },
      end: { dateTime: new Date(endTime).toISOString(), timeZone: "UTC" },
    };

    const response = await calendar.events.insert({
      calendarId: "primary",
      resource: event,
    });

    res.json({ message: "Synced to Google Calendar", eventId: response.data.id });
  } catch (error) {
    console.error("Sync failed:", error);
    res.status(500).json({ error: "Failed to sync event" });
  }
});

app.listen(3000, () => console.log("Google Calendar sync server running on port 3000"));
</script>

How to Set Up Automated Email Reminders for Your Event Calendar App on Replit


<!-- remindersWorker.js -->
<script type="module">
import express from "express";
import { Low } from "lowdb";
import { JSONFile } from "lowdb/node";
import schedule from "node-schedule";
import nodemailer from "nodemailer";

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

// setup lowdb for persistent storage inside Replit
const adapter = new JSONFile("events.json");
const db = new Low(adapter, { events: [] });
await db.read();

// setup mail transporter (use Replit secrets for credentials)
const transporter = nodemailer.createTransport({
  service: "gmail",
  auth: {
    user: process.env.MAIL\_USER,
    pass: process.env.MAIL\_PASS,
  },
});

function scheduleReminders() {
  db.data.events.forEach((event) => {
    if (!event.reminderScheduled) {
      const reminderDate = new Date(event.startTime);
      reminderDate.setMinutes(reminderDate.getMinutes() - 15); // 15 mins before

      if (reminderDate > new Date()) {
        schedule.scheduleJob(reminderDate, async () => {
          await transporter.sendMail({
            from: process.env.MAIL\_USER,
            to: event.ownerEmail,
            subject: `Upcoming Event: ${event.title}`,
            text: `Reminder: "${event.title}" starts at ${event.startTime}.`,
          });
          console.log(`Reminder sent for ${event.title}`);
        });
        event.reminderScheduled = true;
      }
    }
  });
  db.write();
}

app.post("/api/events/reminder", async (req, res) => {
  const { id } = req.body;
  const event = db.data.events.find((e) => e.id === id);
  if (!event) return res.status(404).json({ error: "Event not found" });

  if (!event.reminderScheduled) scheduleReminders();
  res.json({ message: "Reminder scheduled if not already" });
});

scheduleReminders();
app.listen(3000, () => console.log("Reminders worker 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 Event calendar app with Replit

When building an event calendar app in Replit, treat it like a small but complete full-stack project: keep backend, frontend, and configuration clearly separated. Use Replit’s built-in Node.js templates for the server, React (or plain HTML/JS if you prefer) for the user interface, and store environment secrets (like database credentials or API keys) in Replit’s “Secrets” panel — never hard-coded. Persist data with SQLite for local development or a hosted DB like Supabase. Always test in the Replit preview browser and use the Replit Database or external DB when you need persistence. Since Replit gives you one running server per Repl, this app should use one Express server that also serves the frontend and provides JSON APIs for the calendar data.

 

Project Structure

 

Create this structure inside your Replit project:

  • index.js – main server file (Node + Express)
  • public/ – static frontend (HTML, CSS, JS)
  • db.js – database logic (SQLite or Replit DB)

 

// In Replit shell:
npm init -y
npm install express sqlite3
mkdir public
touch index.js db.js public/index.html public/script.js public/style.css

 

Backend Setup (index.js)

 

Add the following code to index.js at the root of the project. This sets up an Express server, serves your static frontend, and exposes REST API endpoints to create and fetch events.

 

// index.js
const express = require('express')
const path = require('path')
const db = require('./db') // import database functions

const app = express()
app.use(express.json()) // allows reading JSON
app.use(express.static(path.join(__dirname, 'public'))) // serves frontend files

// Get all events
app.get('/api/events', async (req, res) => {
  const events = await db.getAllEvents()
  res.json(events)
})

// Add new event
app.post('/api/events', async (req, res) => {
  const { title, date } = req.body
  if (!title || !date) return res.status(400).send('Missing fields')
  await db.addEvent(title, date)
  res.sendStatus(201)
})

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

 

Database (db.js)

 

In Replit, using SQLite is the simplest persistent storage. You can switch to Replit DB or Supabase later. This file contains all DB operations.

 

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

// Create table if not exists
db.run('CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, date TEXT)')

// Return all events
function getAllEvents() {
  return new Promise((resolve, reject) => {
    db.all('SELECT * FROM events ORDER BY date', [], (err, rows) => {
      if (err) reject(err)
      else resolve(rows)
    })
  })
}

// Add event
function addEvent(title, date) {
  return new Promise((resolve, reject) => {
    db.run('INSERT INTO events (title, date) VALUES (?, ?)', [title, date], function (err) {
      if (err) reject(err)
      else resolve()
    })
  })
}

module.exports = { getAllEvents, addEvent }

 

Frontend (public/index.html)

 

This file goes inside the public folder. It displays a list of events and allows adding new ones. Replit automatically serves it when you open the web preview.

 

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <title>Event Calendar</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <h1>My Event Calendar</h1>

  <form id="event-form">
    <input type="text" id="title" placeholder="Event title" required />
    <input type="date" id="date" required />
    <button type="submit">Add Event</button>
  </form>

  <ul id="event-list"></ul>

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

 

Frontend Logic (public/script.js)

 

This script fetches data from the backend and updates the UI dynamically. Use the global Replit preview to test that events show up correctly after adding.

 

// public/script.js
const list = document.getElementById('event-list')
const form = document.getElementById('event-form')

async function loadEvents() {
  const res = await fetch('/api/events')
  const events = await res.json()
  list.innerHTML = ''
  events.forEach(ev => {
    const li = document.createElement('li')
    li.textContent = `${ev.date}: ${ev.title}`
    list.appendChild(li)
  })
}

form.addEventListener('submit', async (e) => {
  e.preventDefault()
  const title = document.getElementById('title').value
  const date = document.getElementById('date').value
  await fetch('/api/events', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ title, date })
  })
  form.reset()
  loadEvents()
})

loadEvents()

 

Best Practices for Replit Deployment

 

  • Keep secrets out of code: use Replit’s “Secrets” tab for database URLs or API keys instead of .env files.
  • Do not rely on local files for permanent storage: SQLite works while the Repl is alive, but if you need persistent or collaborative storage, move to Replit Database or an external hosted DB like Supabase or Firebase.
  • Use “Always On” if possible: your app will stay live for team access or testing.
  • Use the integrated Git pane: commit often, push to GitHub if you expect to collaborate or back up.
  • Organize your code: static assets go in public/, modular backend code in separate files. Avoid mixing frontend and backend logic.

 

Common Pitfalls

 

  • Forgetting to set express.static: if your frontend doesn’t load, ensure you have app.use(express.static('public')).
  • Database resetting: Replit’s storage is ephemeral if the Repl is forked. Always back up or migrate when done.
  • Blocking the main thread: avoid sync functions in Node (especially with SQLite). Always use asynchronous versions like in example above.
  • Expectation mismatch: Replit gives you a full server but not multiple concurrent builds. For scaling or complex scheduling, deploy to an external host when ready (like Render or Railway) using Replit’s Git integration.

 

Following this pattern keeps the code tidy, predictable, and portable — exactly how professional projects grow safely in Replit’s environment.

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