Skip to main content
RapidDev - Software Development Agency
replit-tutorial

How to track user activity in Replit apps

You can track user activity in a Replit-hosted web app by adding lightweight event logging to your frontend, storing events in Replit's built-in PostgreSQL database, and querying them through a simple analytics API. This tutorial covers setting up a logging endpoint, capturing clicks and page views, storing events with timestamps, and viewing aggregated data — all without third-party analytics services.

What you'll learn

  • Create a PostgreSQL table for storing user interaction events
  • Build an Express API endpoint that logs events from the frontend
  • Add a lightweight frontend tracker that captures page views and clicks
  • Query your event data to see aggregated user activity
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner9 min read20-30 minutesAll Replit plans with PostgreSQL support (Core and Pro recommended). Works with React, Express, and any JavaScript-based stack.March 2026RapidDev Engineering Team
TL;DR

You can track user activity in a Replit-hosted web app by adding lightweight event logging to your frontend, storing events in Replit's built-in PostgreSQL database, and querying them through a simple analytics API. This tutorial covers setting up a logging endpoint, capturing clicks and page views, storing events with timestamps, and viewing aggregated data — all without third-party analytics services.

Track User Activity in Your Replit Web App with Event Logging

Understanding how users interact with your application is essential for improving the experience. Instead of integrating heavyweight analytics platforms, you can build a simple event logging system directly in your Replit app. This tutorial walks you through creating a backend endpoint that records user actions, a frontend utility that sends events automatically, and a database table that stores everything for later analysis.

Prerequisites

  • A Replit account on Core or Pro plan (for PostgreSQL access)
  • A running web application with an Express backend and React frontend
  • Basic familiarity with SQL and JavaScript
  • PostgreSQL database enabled in your Replit App (Cloud tab -> Database)

Step-by-step guide

1

Create the events table in PostgreSQL

Open your Replit App's database by clicking the Cloud tab (the plus icon next to Preview), then selecting Database. Use the SQL runner or Drizzle Studio to create a table that stores event data. Each row captures the event type (page_view, click, form_submit), the page or element involved, a session identifier, and a timestamp. The session_id helps group events from the same user visit without requiring authentication.

typescript
1CREATE TABLE IF NOT EXISTS user_events (
2 id SERIAL PRIMARY KEY,
3 event_type VARCHAR(50) NOT NULL,
4 page VARCHAR(255),
5 element VARCHAR(255),
6 session_id VARCHAR(100),
7 metadata JSONB DEFAULT '{}',
8 created_at TIMESTAMP DEFAULT NOW()
9);
10
11CREATE INDEX idx_events_type ON user_events(event_type);
12CREATE INDEX idx_events_created ON user_events(created_at);

Expected result: The user_events table is created in your PostgreSQL database with indexes for fast querying.

2

Build the logging API endpoint

Add a POST endpoint to your Express server that receives event data from the frontend and inserts it into the database. Use parameterized queries to prevent SQL injection. The endpoint should validate that event_type is present and return a 201 status on success. Keep the endpoint lightweight with no authentication requirement so it does not slow down the user experience. Rate limiting is optional but recommended for production apps to prevent abuse.

typescript
1// server/routes/analytics.js
2import { Router } from 'express';
3import pg from 'pg';
4
5const router = Router();
6const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
7
8router.post('/api/events', async (req, res) => {
9 const { event_type, page, element, session_id, metadata } = req.body;
10
11 if (!event_type) {
12 return res.status(400).json({ error: 'event_type is required' });
13 }
14
15 try {
16 await pool.query(
17 `INSERT INTO user_events (event_type, page, element, session_id, metadata)
18 VALUES ($1, $2, $3, $4, $5)`,
19 [event_type, page, element, session_id, JSON.stringify(metadata || {})]
20 );
21 res.status(201).json({ success: true });
22 } catch (err) {
23 console.error('Event logging error:', err.message);
24 res.status(500).json({ error: 'Failed to log event' });
25 }
26});
27
28export default router;

Expected result: POST requests to /api/events insert a row into user_events and return a 201 status.

3

Create a frontend tracking utility

Create a small JavaScript module that the frontend imports to send events. Generate a unique session ID on page load using crypto.randomUUID() and include it with every event. The trackEvent function sends a non-blocking fetch request to your logging endpoint. Use navigator.sendBeacon for page unload events to ensure the request completes even if the user navigates away. Wrap everything in a try-catch so tracking errors never break the application.

typescript
1// src/utils/tracker.ts
2const SESSION_ID = crypto.randomUUID();
3const API_URL = '/api/events';
4
5export function trackEvent(
6 eventType: string,
7 element?: string,
8 metadata?: Record<string, unknown>
9) {
10 try {
11 const payload = {
12 event_type: eventType,
13 page: window.location.pathname,
14 element,
15 session_id: SESSION_ID,
16 metadata
17 };
18
19 fetch(API_URL, {
20 method: 'POST',
21 headers: { 'Content-Type': 'application/json' },
22 body: JSON.stringify(payload)
23 }).catch(() => {}); // Silently ignore tracking failures
24 } catch {
25 // Never let tracking break the app
26 }
27}
28
29export function trackPageView() {
30 trackEvent('page_view');
31}

Expected result: Importing and calling trackEvent or trackPageView sends event data to your API without affecting app performance.

4

Add automatic page view tracking to your React app

Import the trackPageView function into your main App component or layout and call it inside a useEffect hook. If you use React Router, listen for route changes to track every page navigation, not just the initial load. This gives you a complete picture of which pages users visit and in what order, with zero manual effort after the initial setup.

typescript
1// src/App.tsx
2import { useEffect } from 'react';
3import { useLocation } from 'react-router-dom';
4import { trackPageView } from './utils/tracker';
5
6function App() {
7 const location = useLocation();
8
9 useEffect(() => {
10 trackPageView();
11 }, [location.pathname]);
12
13 return (
14 // ... your routes and layout
15 );
16}

Expected result: Every page navigation in your app automatically sends a page_view event to your database.

5

Add click tracking to key UI elements

For important actions like button clicks, form submissions, and link clicks, call trackEvent directly in your event handlers. Pass a descriptive element name and any relevant metadata. Focus on tracking actions that matter for your business — sign-up clicks, purchase buttons, feature usage — rather than every single click. Over-tracking creates noise that makes the data harder to analyze.

typescript
1// Example: tracking a button click
2import { trackEvent } from '../utils/tracker';
3
4function SignUpButton() {
5 const handleClick = () => {
6 trackEvent('click', 'signup_button', { plan: 'pro' });
7 // ... proceed with sign-up logic
8 };
9
10 return <button onClick={handleClick}>Sign Up</button>;
11}

Expected result: Clicking tracked UI elements sends click events with element names and metadata to your database.

6

Build a simple analytics query endpoint

Add a GET endpoint that returns aggregated event data so you can see how users interact with your app. Group events by type and count them, or filter by date range. Protect this endpoint with a simple secret key check so only you can access the analytics data. Query the endpoint from Shell using curl or build a simple admin page.

typescript
1// GET /api/analytics?days=7
2router.get('/api/analytics', async (req, res) => {
3 const adminKey = req.headers['x-admin-key'];
4 if (adminKey !== process.env.ANALYTICS_ADMIN_KEY) {
5 return res.status(403).json({ error: 'Unauthorized' });
6 }
7
8 const days = parseInt(req.query.days) || 7;
9
10 try {
11 const result = await pool.query(
12 `SELECT event_type, COUNT(*) as count,
13 COUNT(DISTINCT session_id) as unique_sessions
14 FROM user_events
15 WHERE created_at > NOW() - INTERVAL '1 day' * $1
16 GROUP BY event_type
17 ORDER BY count DESC`,
18 [days]
19 );
20 res.json({ period_days: days, events: result.rows });
21 } catch (err) {
22 console.error('Analytics query error:', err.message);
23 res.status(500).json({ error: 'Query failed' });
24 }
25});

Expected result: Calling GET /api/analytics?days=7 with the correct admin key returns aggregated event counts grouped by type.

Complete working example

src/utils/tracker.ts
1// src/utils/tracker.ts — Lightweight user interaction tracker
2// Sends events to your backend without blocking the UI
3
4const SESSION_ID = crypto.randomUUID();
5const API_URL = '/api/events';
6
7interface TrackingMetadata {
8 [key: string]: string | number | boolean | null;
9}
10
11export function trackEvent(
12 eventType: string,
13 element?: string,
14 metadata?: TrackingMetadata
15): void {
16 try {
17 const payload = {
18 event_type: eventType,
19 page: window.location.pathname,
20 element: element || null,
21 session_id: SESSION_ID,
22 metadata: metadata || {}
23 };
24
25 // Fire and forget — never await this
26 fetch(API_URL, {
27 method: 'POST',
28 headers: { 'Content-Type': 'application/json' },
29 body: JSON.stringify(payload)
30 }).catch(() => {
31 // Silently ignore tracking failures
32 });
33 } catch {
34 // Never let tracking break the application
35 }
36}
37
38export function trackPageView(): void {
39 trackEvent('page_view');
40}
41
42export function trackClick(element: string, metadata?: TrackingMetadata): void {
43 trackEvent('click', element, metadata);
44}
45
46export function trackFormSubmit(formName: string, metadata?: TrackingMetadata): void {
47 trackEvent('form_submit', formName, metadata);
48}
49
50// Use sendBeacon for events that must fire on page unload
51export function trackBeforeUnload(): void {
52 window.addEventListener('beforeunload', () => {
53 const payload = JSON.stringify({
54 event_type: 'session_end',
55 page: window.location.pathname,
56 session_id: SESSION_ID,
57 metadata: {}
58 });
59 navigator.sendBeacon(API_URL, payload);
60 });
61}

Common mistakes when tracking user activity in Replit apps

Why it's a problem: Awaiting the tracking fetch call, which adds network latency to every user interaction

How to avoid: Call fetch without await and add .catch(() => {}) to silently handle failures. Tracking should be non-blocking.

Why it's a problem: Logging personally identifiable information (email, name, IP address) in the metadata field without user consent

How to avoid: Only log anonymous behavioral data (event type, page, element, session ID). If you need PII, add a privacy policy and consent mechanism first.

Why it's a problem: Not adding database indexes, causing analytics queries to slow down as the events table grows

How to avoid: Create indexes on event_type and created_at when you create the table. For large datasets, consider partitioning by date.

Best practices

  • Never let tracking code crash your application — wrap all tracking calls in try-catch and silently ignore failures
  • Use fire-and-forget fetch calls (do not await) so tracking adds zero latency to user interactions
  • Store analytics admin keys in Replit Secrets, not in code, and protect analytics endpoints with authentication
  • Track meaningful business events (sign-ups, purchases, feature usage) rather than every mouse click
  • Use consistent snake_case naming for event types and element names to simplify querying
  • Include a session_id with every event so you can reconstruct user journeys without requiring login
  • Add database indexes on event_type and created_at columns for fast aggregation queries
  • Respect user privacy: do not log personally identifiable information unless you have explicit consent and a privacy policy

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I have a React + Express app hosted on Replit with a PostgreSQL database. Help me build a lightweight user analytics system that tracks page views and button clicks. I need a database schema, an Express API endpoint for logging events, a React tracking utility, and a query endpoint for viewing aggregated data.

Replit Prompt

Add a user interaction tracking system to my app. Create a user_events table in my PostgreSQL database with columns for event_type, page, element, session_id, metadata (JSONB), and created_at. Add a POST /api/events endpoint to the Express server. Create a tracker utility in src/utils/tracker.ts that sends events without blocking the UI. Add page view tracking to the main App component.

Frequently asked questions

For simple apps, a custom tracker is lighter, faster, and gives you full control over your data. Google Analytics adds external JavaScript that can slow page loads and raises privacy concerns. If you need advanced features like funnels, cohorts, and heatmaps, a third-party tool may be worth the tradeoff.

Each event row is typically 200 to 500 bytes. At 1,000 events per day, you would use roughly 15 MB per month, well within Replit's 10 GB PostgreSQL limit. Add a cleanup job to delete events older than 90 days if storage becomes a concern.

Not if implemented correctly. The tracker uses fire-and-forget fetch calls that run in the background. The API endpoint inserts one row per event, which PostgreSQL handles in under a millisecond. Users will not notice any performance impact.

The session_id generated by crypto.randomUUID() identifies a browsing session without requiring authentication. Each page load creates a new session ID, so you can track behavior patterns without knowing who the user is.

Yes. Connect to your PostgreSQL database from Shell and use pg_dump or COPY TO to export event data as CSV or SQL. You can also build an API endpoint that returns data in JSON format for import into external tools.

Yes. RapidDev can help design a scalable analytics pipeline with proper data warehousing, real-time dashboards, and privacy-compliant tracking for Replit-hosted applications that have outgrown simple event logging.

Add a consent banner that sets a cookie or localStorage flag when the user accepts tracking. Check this flag before calling any trackEvent functions. If the user declines, do not send any events. Store the consent status as metadata on the first event after acceptance.

No. Static Deployments serve only HTML, CSS, and JavaScript files — there is no backend to receive events. You need an Autoscale or Reserved VM deployment for the API endpoint. Alternatively, send events to an external service.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.