I have been playing around with ChatGPT for quite some time now and have used it for quite a few things like blog posts, generation of thumbnails, simple programs, random research on certain topics, etc,. Now I wanted to see just how useful ChatGPT can be if I am starting a project from scratch. I started working on this project called Echoes of Eternity. The app sounds very fancy but it’s actually very simple. It can

  1. Give you a random quote of the day when you access /qotd endpoint
  2. List all the quotes that are present in database when you hit /quotes endpoint

Let’s dive straight into it. Here goes my first prompt

I need a Go program that will respond to /qotd endpoint with a Quote json. Quote is a struct holding author and a quote.

ChatGPT:

package main

import (
	"encoding/json"
	"net/http"
)

type Quote struct {
	Author string `json:"author"`
	Quote  string `json:"quote"`
}

func main() {
	http.HandleFunc("/qotd", quoteOfTheDay)
	http.ListenAndServe(":8080", nil)
}

func quoteOfTheDay(w http.ResponseWriter, r *http.Request) {
	quote := Quote{
		Author: "Mark Twain",
		Quote:  "Good friends, good books, and a sleepy conscience: this is the ideal life.",
	}

	json, err := json.Marshal(quote)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(json)
}

As you can see it knows that qotd is quote of the day and that it will need a Quote struct. Awesome! Now I want quote fetched from database so here goes another prompt

Now update above code to read a random quote from quotes.db

ChatGPT:

func quoteOfTheDay(w http.ResponseWriter, r *http.Request) {
	db, err := sql.Open("sqlite3", "quotes.db")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	var count int
	err = db.QueryRow("SELECT COUNT(*) FROM quotes").Scan(&count)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	var idx = rand.Intn(count)
	var quote Quote
	err = db.QueryRow("SELECT author, quote FROM quotes WHERE id = ?", idx).Scan(&quote.Author, &quote.Quote)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	json, err := json.Marshal(quote)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(json)
}

The code is syntactically correct but I really don’t want database initialized in my request handler

I would prefer that the database initialization happens in main. Just give me code without the explanation

ChatGPT:

var db *sql.DB

func main() {
	var err error
	db, err = sql.Open("sqlite3", "quotes.db")
	if err != nil {
		panic(err)
	}
	defer db.Close()

	http.HandleFunc("/qotd", quoteOfTheDay)
	http.ListenAndServe(":8080", nil)
}

It uses the global variable in the handler function. Quite good but I have seen it initializing database without global variable. I actually prefer that way but for now this works.

We need to create quotes.db. Can you provide me with some dummy data for this database

ChatGPT:

CREATE TABLE quotes (id INTEGER PRIMARY KEY, author TEXT, quote TEXT);

INSERT INTO quotes (author, quote) VALUES
("Mark Twain", "Good friends, good books, and a sleepy conscience: this is the ideal life."),
("Albert Einstein", "Imagination is more important than knowledge."),
("Mahatma Gandhi", "Be the change that you wish to see in the world."),
("Steve Jobs", "Your work is going to fill a large part of your life, and the only way to be truly satisfied is to do what you believe is great work. And the only way to do great work is to love what you do. If you haven't found it yet, keep looking. Don't settle. As with all matters of the heart, you'll know when you find it."),
("Henry Ford", "Whether you think you can, or you think you can't - you're right.");

So now our app can at least respond with random quote. Let’s add some more capability to it

Now give me an API that will list all the quotes on certain endpoint

It adds http.HandleFunc("/quotes", listQuotes) in main and then goes on to give implementation of listQuotes

ChatGPT

func listQuotes(w http.ResponseWriter, r *http.Request) {
	rows, err := db.Query("SELECT id, author, quote FROM quotes")
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer rows.Close()

	var quotes []Quote
	for rows.Next() {
		var quote Quote
		err := rows.Scan(&quote.ID, &quote.Author, &quote.Quote)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}
		quotes = append(quotes, quote)
	}

	json, err := json.Marshal(quotes)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(json)
}

These are just endpoints we need some front-end to view our quotes

Now give me a HTML homepage that can list all the quotes available at /quotes endpoint

ChatGPT

<html>
<head>
	<title>Quotes</title>
</head>
<body>
	<h1>Quotes</h1>
	<ul id="quote-list"></ul>
	<script>
		fetch("/quotes")
			.then(response => response.json())
			.then(quotes => {
				const quoteList = document.getElementById("quote-list");
				for (const quote of quotes) {
					const li = document.createElement("li");
					li.innerHTML = `"${quote.quote}" - ${quote.author}`;
					quoteList.appendChild(li);
				}
			});
	</script>
</body>
</html>

Along with above HTML the response also included updated Go code to serve above html

	http.HandleFunc("/", homepage)
	http.HandleFunc("/qotd", quoteOfTheDay)
	http.HandleFunc("/quotes", listQuotes)
	http.ListenAndServe(":8080", nil)
}

func homepage(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "index.html")
}

Now we can view quotes when we hit our website but still the HTML is too barebones. Let’s stylize it. Below was my prompt

Can you stylize the homepage a little bit? Maybe use card for each quote

It’s initial approach was to use bootstrap and it’s response was truncated so I’m not including it here. I threw another prompt at it

I would prefer not using bootstrap

ChatGPT:

<html>
<head>
	<title>Quotes</title>
	<style>
		.card {
			border: 1px solid #ccc;
			border-radius: 4px;
			box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
			display: flex;
			flex-direction: column;
			overflow: hidden;
		}
		.card-body {
			flex: 1;
			padding: 16px;
		}
		.card-text {
			margin: 0;
		}
		.card-footer {
			background-color: #f7f7f7;
			border-top: 1px solid #ccc;
			padding: 16px;
		}
	</style>
</head>
<body>
	<h1>Quotes</h1>
	<div id="quote-list"></div>
	<script>
		fetch("/quotes")
			.then(response => response.json())
			.then(quotes => {
				const quoteList = document.getElementById("quote-list");
				for (const quote of quotes) {
					const card = document.createElement("div");
					card.className = "card";
					card.innerHTML = `
						<div class="card-body">
							<p class="card-text">"${quote.quote}"</p>
						</div>
						<div class="card-footer">
							<p class="card-text">- ${quote.author}</p>
						</div>
					`;
					quoteList.appendChild(card);
				}
			});
	</script>
</body>
</html>

This was good but I had to do couple of iterations with it to fine tune design of my webpage. I am not going to include all responses but here are all the prompts I used

I would prefer that card does not extend throughout the screen and remains centered. Also, provide me the updated parts of code only and not entire listing

That was incomplete response. I would prefer that you respond with just the changes that are required in code for my previous request

what css I need to update if I want to have spacing between 2 cards

Now I need a good header for this page. You can center the heading and change it to “echoes of eternity”. Stylize the header. You can just provide code related to header

Add some color to header and choose some nice font

The final output

<html>
<head>
	<title>Quotes</title>
	<style>
		.header {
			color: #00bfff;
			font-family: "Courier New", monospace;
			font-size: 32px;
			font-weight: bold;
			margin: 32px 0;
			text-align: center;
			text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
		}
		.card-wrapper {
			display: flex;
			flex-wrap: wrap;
			justify-content: center;
			max-width: 800px;
			margin: 0 auto;
		}
		.card {
			border: 1px solid #ccc;
			border-radius: 4px;
			box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
			display: flex;
			flex-direction: column;
			overflow: hidden;
			max-width: 300px;
			margin: 16px;
		}
		.card-body {
			flex: 1;
			padding: 16px;
		}
		.card-text {
			margin: 0;
		}
		.card-footer {
			background-color: #f7f7f7;
			border-top: 1px solid #ccc;
			padding: 16px;
		}
	</style>
</head>
<body>
	<h1 class="header">Echoes of Eternity</h1>
	<div class="card-wrapper">
		<div id="quote-list"></div>
	</div>
	<script>
		fetch("/quotes")
			.then(response => response.json())
			.then(quotes => {
				const quoteList = document.getElementById("quote-list");
				for (const quote of quotes) {
					const card = document.createElement("div");
					card.className = "card";
					card.innerHTML = `
						<div class="card-body">
							<p class="card-text">"${quote.quote}"</p>
						</div>

As you can see the response is not complete. But you can get it to do what you want by requesting only the parts of code you are interested in. I have seen it truncating response when the code listing is long. I have observed that this mostly happens with HTML code only. Anyways, entire code listing is available on my GitHub

Except some minor refactoring of code I did not do any changes to the generated code. All of this in under 30 mins and I know just as much CSS as a backend developer would. Here is the screenshot of the app that we designed. It’s very basic but it’s generated by machine!

Subscribe for more!