How I use ChatGPT to prototype
November 29, 2023 in development, tech

I have been using ChatGPT for just about a year now, especially since GPT-4 was released. It is not the best tool for everything, but it is great when you just have an idea and want to explore the possibility. AKA, prototyping.

How to start:

Talk to ChatGPT like it’s a senior developer with dependency issues, and you’re their tech lead. By dependency issues, I mean that they get stuck and need a lot of prodding, but when you get them pointed on exactly the right problem, they know their stuff.

It’s important to have that attitude, because that mindset will get you to describe the problem in just the way that ChatGPT needs to give a good output.

Here’s an example.

I wanted to prototype something new related to work. We use a React app with a Rails backend, and I hit my wits end today with data not synchronizing properly when multiple users are working on the same page.

I decided that I wanted to experiment with a real-time UI for data fields, mostly just to see how it could work.

My initial prompt to start the conversation was:

OK I want to brainstorm a new website architecture, and you’re going to give me the quickest and easiest technologies to accomplish this goal.

Back in the 90s and 00s, a lot of data-management technology was based on terminal inputs.

There was one mainframe, and the users would connect through a terminal. All changes that the user made were reflected immediately on the server-side. There was no local state, everything was managed within one box. Even the state of the UI. So, if one user made a change, it was immediately reflected on the terminals of other users, since they were all working off of the same computer.

Today in websites, there is an issue of keeping data in sync between server & client. If UserA updates a field, then a whole synchronization process has to happen to update that information on UserB’s client. If one user has a slower connection, then it can cause data conflicts that must be managed.

I want to design a website that can manage data in the same way that old terminal apps worked. I want the webpage to act as a “dumb terminal” with a fancy UI layer above it. But, in this scenario, the UI state is all still managed on a single server, and has a realtime connection with the website frontend.

What technologies can I use to make this possible. Can I have a totally server-driven UI like this without introducing too much lag for the users? Can I still use React?

Phew, that was a lot! But I want you to notice how specific that I was about certain things. I described in deep details specifically what the “dependent senior developer” needs to do. I gave enough context to describe the problem without getting too deep into details.

It responded with a long message with a few different sections. Shortened for brevity, it was something like:

ChatGPT:

Your concept of a website architecture that emulates the dynamics of old terminal-based systems, with server-side management of UI state and real-time updates, is both innovative and challenging. To implement this, you’ll need technologies that support real-time communication, efficient server-side rendering, and possibly a front-end framework that can handle dynamic updates. Here’s a breakdown of the technologies and approaches you might consider:

Backend, Frontend, Architecture Considerations, Potential Challenges

… huge list of details about technologies, architecture, etc. It suggested WebSockets and Golang …

This is great, because it wrote out an architecture plan and gave me a few technologies that would be useful. ChatGPT will load up all past messages before making a new response. So having that detailed architecture plan in the history from this point on is useful, because ChatGPT will be primed with all of that context in the next message.

The key is to get as much information as possible ready to go, so that it can make an informed decision about what the code should look like. If you did this right, you’ll really only need to skim through the long ChatGPT response since it doesn’t matter very much what it actually says. As long as nothing stands out as bad, and it has a lot of design detail.

Now it’s time to use all of that pre-loaded information to get some code out. Here’s what I wrote next:

Given all of that information, please create a bare-bones minimal prototype application, with all necessary code required to render a single textbox on the server-side and have multiple clients able to edit the contents of that textbox simultaneously.

What did I do there? Well, I re-stated the problem but in the form of an extremely simple proof-of-concept. The first message was grandiose and vague, with the intention of getting it to reply with a solid foundation of architecture and technology decisions, but the second message was small and specific, to try to apply that knowledge.

Here’s what it gave me.

ChatGPT:

Creating a minimal prototype where multiple clients can edit a single textbox simultaneously with server-side rendering and real-time updates involves setting up both a backend server and a frontend client. I’ll guide you through a basic setup using Go for the backend and React for the frontend, along with WebSockets for real-time communication.

Backend (Go)

We’ll create a simple Go server that uses WebSockets to communicate with clients. For simplicity, I’ll use the gorilla/websocket package, which is a popular choice for WebSocket implementations in Go.

  1. Install Gorilla WebSocket: First, ensure you have the Gorilla WebSocket package installed.
go get -u github.com/gorilla/websocket
  1. Go Server Code:
package main

import (
    "log"
    "net/http"
    "github.com/gorilla/websocket"
)

var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan string)
var upgrader = websocket.Upgrader{}

func main() {
    http.HandleFunc("/ws", handleConnections)
    go handleMessages()
    log.Println("Server started on :8000")
    err := http.ListenAndServe(":8000", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}

func handleConnections(w http.ResponseWriter, r *http.Request) {
    upgrader.CheckOrigin = func(r *http.Request) bool { return true }
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

    clients[conn] = true

    for {
        _, msg, err := conn.ReadMessage()
        if err != nil {
            delete(clients, conn)
            break
        }
        broadcast <- string(msg)
    }
}

func handleMessages() {
    for {
        msg := <-broadcast
        for client := range clients {
            err := client.WriteMessage(websocket.TextMessage, []byte(msg))
            if err != nil {
                client.Close()
                delete(clients, client)
            }
        }
    }
}

Frontend (React)

For the frontend, we’ll create a simple React app that communicates with the Go server via WebSockets.

  1. Create React App: If you haven’t already, create a new React app:
npx create-react-app realtime-textbox
cd realtime-textbox
  1. React Component: Edit src/App.js to include a basic textbox and WebSocket communication logic:
import React, { useState, useEffect } from 'react';

function App() {
    const [text, setText] = useState('');
    const [ws, setWs] = useState(new WebSocket('ws://localhost:8000/ws'));

    useEffect(() => {
        ws.onopen = () => {
            console.log('WebSocket Connected');
        };

        ws.onmessage = (e) => {
            setText(e.data);
        };

        return () => {
            ws.onclose = () => {
                console.log('WebSocket Disconnected');
                setWs(new WebSocket('ws://localhost:8000/ws'));
            };
        };
    }, [ws.onmessage, ws.onopen, ws.onclose]);

    const handleTextChange = (e) => {
        const value = e.target.value;
        setText(value);
        ws.send(value);
    };

    return (
        <div className="App">
            <textarea value={text} onChange={handleTextChange} />
        </div>
    );
}

export default App;
  1. Run the React App: Start the React development server:
npm start

Running the Prototype

  1. Start the Go Server: Run your Go server.
  2. Open the React App: Open http://localhost:3000 in multiple browser windows or tabs.
  3. Test Real-time Updates: Start typing in the textbox in one window and observe the changes being reflected in real-time in other windows.

This setup provides a minimal, bare-bones example of a server-driven UI with real-time updates across multiple clients. You can build upon this foundation to add more complex features and optimizations as needed for your specific application.

Ok, now, if you’re following along at home (which I’m positive you aren’t) you’ll notice that this literally works.

Sure, it’s a bit hacky and doesn’t organize the code into modules or components. But it is a start. It gives us a basic framework to start building off of. You can copy-paste that code into a repo, and git commit -m "initial commit".

From here, you can take over. Start making changes to get it to do what you want. When you hit a snag, you can remind ChatGPT of the relevant context and ask it for follow-up – especially if you hang on to your session that had this priming. If you’re like me, you can sometimes get lost in the details of trying to get a project started, and this can be a good jump to get to something that works, even if it’s super simple.