DEV Community

Steven Nguyen
Steven Nguyen

Posted on

Building a Real-Time Spreadsheet App with React, NestJS

I’ve always loved the flexibility of Excel — but when teams need to collaborate in real time, spreadsheets can quickly become a mess of conflicting versions and broken formulas.

So I decided to build something different: a web-based collaborative spreadsheet app, where multiple users can view and edit data directly from the browser — powered by React, Handsontable, NestJS, GraphQL, and MongoDB.

This article walks you through the architecture, setup, and lessons learned from building this project.


💡 The Goal

The idea was simple:

  • A web interface that feels like Excel
  • A backend API to handle CRUD operations
  • A database to persist sheet data
  • Real-time synchronization between users

🧱 The Tech Stack

Here’s what I chose and why:

  • React + Handsontable → To create a dynamic, spreadsheet-like UI
  • NestJS → For a structured, TypeScript-based backend
  • GraphQL → To efficiently fetch and mutate sheet data
  • MongoDB → To store sheet contents and metadata flexibly

This combination gives both speed and scalability — perfect for small SaaS apps or internal tools.


⚙️ Step 1: Setting Up the Backend (NestJS + GraphQL + MongoDB)

Create a new NestJS project:

npm i -g @nestjs/cli
nest new excel-share-api
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

npm install @nestjs/graphql @nestjs/apollo graphql apollo-server-express mongoose @nestjs/mongoose
Enter fullscreen mode Exit fullscreen mode

app.module.ts

import { Module } from '@nestjs/common';
import { GraphQLModule } from '@nestjs/graphql';
import { MongooseModule } from '@nestjs/mongoose';
import { SheetModule } from './sheet/sheet.module';

@Module({
  imports: [
    GraphQLModule.forRoot({
      autoSchemaFile: true,
      playground: true,
    }),
    MongooseModule.forRoot('mongodb://localhost:27017/excel_share'),
    SheetModule,
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

🧩 Step 2: Create a Sheet Schema and Resolver

sheet.schema.ts

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

@Schema()
export class Sheet extends Document {
  @Prop({ required: true })
  name: string;

  @Prop({ type: Object })
  data: Record<string, any>;
}

export const SheetSchema = SchemaFactory.createForClass(Sheet);
Enter fullscreen mode Exit fullscreen mode

sheet.resolver.ts

import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { SheetService } from './sheet.service';
import { Sheet } from './sheet.model';

@Resolver(() => Sheet)
export class SheetResolver {
  constructor(private sheetService: SheetService) {}

  @Query(() => [Sheet])
  async sheets() {
    return this.sheetService.findAll();
  }

  @Mutation(() => Sheet)
  async updateSheet(
    @Args('id') id: string,
    @Args('data') data: string
  ) {
    return this.sheetService.update(id, JSON.parse(data));
  }
}
Enter fullscreen mode Exit fullscreen mode

Now your backend is ready to query and update spreadsheet data in MongoDB through GraphQL.


💻 Step 3: Building the Frontend (React + Handsontable + Apollo Client)

Create the React app and install dependencies:

npx create-react-app excel-share-frontend
npm install handsontable @handsontable/react @apollo/client graphql
Enter fullscreen mode Exit fullscreen mode

ExcelSheet.js

import React, { useState, useEffect } from 'react';
import { HotTable } from '@handsontable/react';
import { useQuery, useMutation, gql } from '@apollo/client';

const GET_SHEET = gql`
  query {
    sheets {
      _id
      name
      data
    }
  }
`;

const UPDATE_SHEET = gql`
  mutation UpdateSheet($id: String!, $data: String!) {
    updateSheet(id: $id, data: $data) {
      _id
      name
      data
    }
  }
`;

function ExcelSheet() {
  const { data } = useQuery(GET_SHEET);
  const [updateSheet] = useMutation(UPDATE_SHEET);
  const [sheetData, setSheetData] = useState([]);

  useEffect(() => {
    if (data && data.sheets.length > 0) {
      setSheetData(data.sheets[0].data);
    }
  }, [data]);

  const handleChange = async (changes, source) => {
    if (source === 'loadData') return;
    await updateSheet({
      variables: {
        id: data.sheets[0]._id,
        data: JSON.stringify(sheetData),
      },
    });
  };

  return (
    <div>
      <h2>{data?.sheets[0]?.name}</h2>
      <HotTable
        data={sheetData}
        colHeaders={true}
        rowHeaders={true}
        afterChange={handleChange}
        licenseKey="non-commercial-and-evaluation"
      />
    </div>
  );
}

export default ExcelSheet;
Enter fullscreen mode Exit fullscreen mode

Now you have a live spreadsheet UI that syncs changes through GraphQL to MongoDB.


⚡ Step 4: Adding Real-Time Collaboration (Optional)

To make it collaborative, integrate GraphQL Subscriptions or WebSockets in NestJS.

Each time a user updates a cell, broadcast that update to all connected clients.

Example setup:

  • Use @nestjs/websockets for socket updates
  • Broadcast using Socket.io
  • On the frontend, listen for events and refresh the table

🧠 Lessons Learned

  • Handsontable is powerful — but you must handle performance when data grows.
  • GraphQL fits perfectly for partial data updates and flexible queries.
  • MongoDB’s document model makes it easy to store dynamic sheet data.
  • Clear data versioning and change tracking are critical for collaboration.

🚀 Final Thoughts

Building an Excel-like collaboration tool is challenging but rewarding.

With React, NestJS, GraphQL, and MongoDB, you can achieve both flexibility and performance — and Handsontable gives your users the familiar spreadsheet experience they love.

This setup can easily become the foundation for:

  • Internal business tools
  • Data analysis dashboards
  • Lightweight SaaS spreadsheet applications

All running with one unified stack — TypeScript end to end.


👨‍💻 Written by Steven Nguyen — Engineering Manager | Building scalable enterprise systems with Java, NestJS, and modern DevOps practices.

Top comments (1)

Collapse
 
hashbyt profile image
Hashbyt

Step by step approach to creating a flexible, scalable spreadsheet app with popular JS technologies. Highlights include schema design, UI binding, and strategies for live collaboration.
Great reference for developers building interactive enterprise tools.