- Faz's Paradox
- Posts
- Learning to Build a Webapp in 7 Days with LLMs
Learning to Build a Webapp in 7 Days with LLMs
How I built a webapp from scratch using LLMs, navigating challenges, and transforming my dev skills with AI-powered problem-solving.
Welcome,
You’re reading Faz’s Paradox — an intently slow newsletter exploring all that interests us, from social norms and philosophy, to finance and coding. Every issue covers something different, but is purposefully off the beaten path. Let’s dive in.
Table of Contents
Update 20/06/2025
The website which Yeeter was hosted on (farzan.app) may no longer serve you Yeeter. This is mostly due to content moderation concerns, I have no way of meaningfully moderate what was posted on there.
It is likely now serving something completely different - It is a playground for me to try new things and follow my other interests.
Introduction and Background
This post is a companion piece with another post I am working on about how we’ve forgetting how to learn. That piece serves as the analytical side to this piece. Admittedly it is a slight departure from my previous posts, but that’s why you’re here right?
It is a bit embarrassing that after studying computer science and doing a masters in cybersecurity that I don’t know how to make a website that does things. Since then I have taught myself a mix of things, how to build and use APIs, how to deploy blockchain infrastructure, and how to make a static website. But I don’t know how to make a website that actually lets the user log in, do things, and see the things other people have done.
I am dedicating a week to make a web app with some key functionality, and it will likely imitate an existing website. Ultimately, the goal is to unlock blockers in my mind about making things.
Here is a list of things I want to learn and don’t already know how to do:
Authenticate users - Probably using Google Sign-In.
Store relevant user data on my database.
Allow users to edit and manipulate data.
Implement security features - Like preventing SQL injection attacks.
I will also be using AI/LLM tools to help me. Namely Perplexity Pro and Claude Pro. I do think these tools are extremely useful and powerful. It is like having an extremely smart friend at your fingertips. You can also assume most of the things I did were because Perplexity had suggested it.
Day 1 - Tuesday June 3rd 2025
Despite this being Day 1, it was closer to Night 1 as I only began work at around 9pm.
Authentication
- The process of proving that something is real, true, or what people say it is
- The process of proving, especially using a computer or other electronic device, that someone is who they claim to be, that they have the right to make a payment (or in this case, send data)
Before I could authenticate users, I wanted to understand the relationship between authenticating using Google Sign-In and data that gets stored on a database. Perplexity’s response below.

Perplexity’s response was what I expected, it was good to get a second opinion.
Perplexity helped me generate a website using only HTML, CSS, and JavaScript to allow Google login functionality. I created the file structure, using VSCode as my development environment. And launched a Python web server to test out the website.

I had Python installed from before
After going to http://localhost:8000/ in my browser, I was presented with the website below. It was a big page with a small widget in the middle to ‘Sign in with Google’. Exactly what I wanted.

The button alone launched a window which allows me to choose a Google account to sign-in with, but it didn’t work because there was no client ID currently associated with it. The client ID is a way for Google to identify your website when you request a user’s information.
I was directed to Google’s Authentication Platform as part of Google Cloud where I configured my application URIs so Google knows where to redirect the user’s data to (this is the localhost:8000 part below).

Once this was saved and I tried logging in, my page updated to reflect my profile image, email, and a user ID. Perplexity helped me implement a Sign Out button which just reverts the page to the initial state.

My display picture is just a black circle.
I manually added some code to output some of the data that Google provided into a console (accessible via the F12 menu on most browsers) so I could understand what Google was sharing.

Outputting Google’s response
At this point it was near midnight and I had achieved what I set out to do for the day. I headed to the gym and called it a day.
Day 2 - Wednesday June 4th 2025 (Database setup via MySQL)
Database
- A database is an organized collection of structured information, or data, typically stored electronically in a computer system. A database is usually controlled by a database management system (DBMS).
There are many Database Management Systems (DBMS), each provides different functionality, and can vary in speed, limitations, and ease of implementation. I decided on MySQL for my DBMS, it was one I had used before so it felt natural to engage with. There were some issues with my previous installation so I had to reinstall MySQL from scratch which took more time than expected.
I already had some databases from when I was tinkering with Circle’s (the stablecoin issuer) API.

Perplexity provided me with a script to create a database, and then created a table within that database.

For anyone unfamiliar with SQL, this code first creates a database called userdb. A database can hold many tables, useful for a project with lots of things going in. Within this database, it creates a table called ‘users’ which has 5 properties. id which is unique for each entry in the table.
For anyone unfamiliar with SQL, this above code does a few things:
Creates a database called ‘userdb’ - A database can hold lots of tables (sets of data). This is useful for a projects where you may have lots of tables containing different information. For example, a store might have one table for its customers details and another table for its products and pricing information.
‘USE userdb;’ - Sets the stage for the following pieces of code. Because there are multiple databases, if I didn’t do this, the code would not know where to create the table.
The last section of the code creates the table and outlines properties we need to provide when adding data to the table. For example, id is a number, which must be unique (no other entries in the table can have the same id). Email is a set of characters of length 255, which should not be empty.
I ran this script to test inserting a user into the table - It ran successfully as shown by the message at the bottom.

Inserting a user into the table

I can see that the user is now in the table
I implemented code to which would connect the frontend (index.html) to the backend (server.js) and enable the webapp to write to the database. Initially, it didn’t work and I spent a lot of time troubleshooting with Perplexity. What inevitably fixed the issue was restart VSCode, that was both strange and frustrating.
Day 3 - Thursday June 5th 2025 (Debugging and creating my webapp)
API (Application Programming Interface)
- An application programming interface is a connection between computers or between computer programs. It is a type of software interface, offering a service to other pieces of software. A document or standard that describes how to build such a connection or interface is called an API specification.
When I tried to launch the MySQL Workbench, I received an error that the MySQL service wasn’t running. Manual restarts of the service didn’t fix it, and I was presented with the error below.

I didn’t record all of my troubleshooting steps, but I ended up uninstalling all the MySQL utilities (Workbench, Sever) and reinstalling them. Everything worked smoothly after that, and it was the last time I faced that issues.
I spent some time wondering what app I wanted to make. Reflecting on the criteria I laid out at the beginning:
Authenticate users ✅ Users could sign in with Google.
Store relevant user data ✅ User Sign-In data was stored on my database.
Allow users to edit and manipulate data ❌ In my mind this would either be having a user profile that they can edit, or allowing the user to post on the website.
Implement security features ❌ I didn’t know it at the time but I had implemented SQL injection protection.
I decided on Yeeter, a knock-off version of X/Twitter. It met all my requirements, and is one of my most used social media websites.
In Paint I made a mock-up of the UI that I wanted. This was pasted into Perplexity, alongside a prompt. Both of which are included below.

My mock-up of the app UI, made in Paint
Please create a UI based on the following mock-up. It is an app called 'Yeeter' The key components: - Title in the top right of the platform. It should hyperlink to itself. - In the top right there should be a sign-in with google button. When Signed in it should show a log out button instead. - When logged in, a Profile Picture should be shown to the left of the sign out button. When a user signs in it should store their information in a data, it should also include their last login time. Also provide SQL code to make this table in my database called 'yeeter'. - In the middle of the screen below the title and login button should be a text box where users can write a 'yeet' - Below the text box should be a button called Yeet which writes the following to the database: date and time of the yeet, the sender (based on who is logged in), and the content of the text box. Also provide SQL code to make this table in my database called 'yeeter'. - In the recent yeet sections should be the most recent yeets from the database. It should include the sender's name, date/time of sending, and the content of the tweet continue using only html,css, and javascript. Also make the server.js code.
Me prompting Perplexity to write code based on the mock-ups provided (above)
I recieved a number of outputs:
An SQL script - To create the databases and tables.
Code for index.html - This contained HTML, CSS, and some JavaScript which would call APIs, allowing the user to interact with the website.
Code for server.js - This is how my server would manage API requests and carry out functions based on the API called and data supplied.
At this point my responses were getting cut off and I received a notification that my conversation was getting too long. I created a new chat, uploaded index.html and server.js and asked it to split index.html into a HTML file, a CSS file, and a JavaScript file which replaced the <script> tags in the HTML file. This was mostly done for readability and reusability.
I did get an Error 400 (Bad Request) when submitting a Yeet, which meant the information being sent from the frontend didn’t align with what the backend was expecting.

A successful call to the yeet API.
I asked Perplexity what the issue could be and surprisingly it broke down exactly what it expected to see in index.html and server.js.

I did have a discrepancy - The frontend was sending content
but not google_sub
. Both needed to be sent.
After I amended server.js, I carried out some testing with multiple Gmail accounts I amended the server.js code to reflect what it was expecting and suddenly it worked.

A working Yeeter
Despite the Error 400 mentioned above, the functionality worked amazingly. I could log in, log out, and send a Yeet. I was most surprised by some of the error handling that Perplexity had implemented, Yeets could not be more than 280 characters, and it even tells you how many characters your Yeet is (if more than 280 characters).
I spent the rest of my time that day, understanding the code, asking Perplexity (or Claude) where I didn’t.
Day 4 - Friday June 6th 2025 (Domains, Infrastructure/Hosting, Server Optimisations, and Speed Testing)
DNS (Domain Name Service)
- Domain Name System, is a fundamental part of the internet that translates human-readable domain names (like google.com) into the numerical IP addresses that computers use to identify each other. It means you don't need to remember long IP addresses like 192.168.0.10
Domains - A home on the internet
As it currently was, my code was running on a ‘localserver’, my computer was talking to itself to handle the website, serve information, and execute API requests. In order for Yeeter to be a social media platform, it needed to be accessible to everyone.
The first thing I did today was buy a domain (farzan.app) to host Yeeter. This wasn’t entirely necessary, I had domains which I could have modified to host the app.
When buying the domain, I used the wrong Cloudflare account and had to go through a process of moving domains over. I had to execute the transfer, accept receipt of it on the destination account, and then go through additional set up steps. Little did I know, that was the easy part of the day.
Infrastructure/Hosting - Turning my mini-PC into a web server
What I had just done was create a way for people to access my app through farzan.app. What was not configured was the server that would store index.html, and run server.js.
There are many options for this, I could use a cloud provider where I ‘rent’ a computer from Google, Microsoft, Amazon, or one of the other providers. Alternatively, I could self-host which requires hardware that I would configure and manage myself, this is the option I chose.
I already had a Mini-PC at home which I could dedicate to this cause. Firstly, I configured Cloudflare Proxy (a service Cloudflare offers). This hides my IP address from anyone visiting the website, not doing this is the equivalent of telling strangers where you live.
All web traffic to farzan.app would head towards my Mini-PC with the proxy as a middleman. A crude diagram below:
You <--> farzan.app <--> Cloudflare Proxy <--> My Mini-PC
There was a number of settings I had to configure to ensure my Mini-PC would respond to the incoming traffic. Below are the key steps I had to carry out to enable this.
It took me two tries to get it right. The first time I was inconsistent with the configurations I set on my Mini-PC which caused issues. The second time I was more consistent with the ports I was configuring and it worked straight away.

Key steps to make my Mini-PC accessible via farzan.app
Unfortunately I did not document this process well, not that I was in the mood too since it took me a few hours to get the configuration right. Getting it working was a massive weight off my shoulders as I hate to leave things in a non-working state overnight.
Server Optimisations - Time is money
One thing I despise about a lot of websites is the loading times. Typically, using frameworks and complex animations can make websites slow to load, are not friendly to older hardware, and require advanced mobile device optimisations.
I decided to test the speed of these optimisations using a tool called Artillery which simulates web traffic. I will note that there are some nuances with the optimisation which I will discuss later.

My Artillery function configuration - It would call 50 new users a second for 5 minutes.
The optimisations I tested included:
Lazy Loading - Elements of the page (like images and videos) are not loaded immediately when visiting a page. Rather, they are only loaded when they are close to entering the user’s view. For example, an image at the bottom of a long website does not need to be loaded immediately upon visiting the site.
Defer Script - Similarly to Lazy Loading, Defer Script is a tag in HTML which tells browsers that it is not neccessary to download the script immediately. This again is a useful optimisation as visitors to the website are unlikely to interact with all the parts that require those scripts as soon as it loads.
Compression - This simply reduces the overall file size of the HTML, CSS, and JavaScript sent. It also reduces the network impact as the total amount of files sent is smaller. However, it requires more processing power from the device that is visiting the website to unpack the compressed files.
Multi-Core - Enables the server to distribute requests across various ‘cores’ which can operate independently. This allows web traffic to be managed more effectively without overloading a single process.
Testing Optimisations
Below is a table outlining various metrics of the session length of each request created by Artillery. It tests a number of optimisations. Edit: Fixed the colouring of the table.
No Change | Lazy Loading (LL) | Defer Script (DS) | LL + DS | LL + DS + Compression | LL + DS + Multi-Core | |
---|---|---|---|---|---|---|
min | 57.5 | 62 | 60.8 | 61.1 | 61.2 | 59.1 |
max | 850 | 330.9 | 1349.6 | 346.2 | 1185.2 | 1392.4 |
mean | 95.2 | 92.6 | 93.3 | 93 | 93.6 | 92.5 |
median | 87.4 | 85.6 | 87.4 | 87.4 | 85.6 | 85.6 |
99 percentile | 159.2 | 144 | 141.2 | 144 | 144 | 144 |
Below is a normal distribution plotted based on the above data - It was created by Perplexity so it may not be perfect but it was enough for my to visualise the data.

Normal distribution plotted for the various configurations. The x-axis reads seconds but should be milliseconds.
Between the optimisations, I noted very little difference. This isn’t to say the optimisations don’t work, but rather each optimisation adds a fixed processing time, and due to the small file sizes and lack of complex code of Yeeter, the optimisations help very little.
In the end the optimisation I implemented for Yeeter was a combination of Lazy Loading and Defer Scripting. This has the benefit of not having the extremely large outliers (>1000ms) and helps future proof the application if I was to continue development.
Day 5 - Saturday June 7th 2025 (Security, Logging, and the Bots)
Security - Preventing SQL injection attacks
I knew that one of the biggest issues with ‘vibe coding’ is that it allows for the introduction of security flaws which can result in compromise data. This stems from the fact that most people vibe coding do not understand the code and the risks that are apparent.
One things I was keen on eliminating was SQL Injection Attacks (one of the most frequently exploited vulnerabilities). These are when user inputs can be maliciously crafted and can result in a compromise of the data within the SQL database.
I asked Perplexity on the best approach to eliminate this risk and it suggested using ‘prepared statements’. This is when you define where the user’s inputs will be included in the SQL statement before even accepting an input.

An example of a prepared statement. The question marks represent the user’s inputs which will be stored, represented by the array [user_id, content, now, 0].
This is in contrast to the way I first learnt where the user’s input is placed directly into the SQL statement. A malicious user could write code as their input which is then executed by our server.
Thankfully, Perplexity had used prepared statements from the very beginning of our code writing adventures, meaning we were pretty safe.
Logging - Tracking the bots, the hackers, and the genuine visitors
I think it is also important to capture the details of HTTP requests that my server receives. In a large scale application this can be used to prevent DDoS attacks, identify malicious activity, and improve your services.
I leveraged a library called Morgan, a request logger for JavaScript. Installation and integration into my app was simple, it took a few lines of code to get it to work.

Logs captured by Morgan showing the server fetching the recent Yeets, and also a user logging in (hence the save-user API call).
As optimisation was important to me, it is worth mentioning that from a speed perspective, this type of logging would slow down the server (but not the user experience) as we are storing data on every request made to the website. In this case, I was able to justify impact on speed in favour of security.
What I found interesting through this logging was the number of non-human requests being made to my website. The image below shows a number of attempts at trying to retrieve a WordPress configuration file. A misconfigured config file could presumably contain login details or information which would allow someone to compromise the website.

Attempts by (presumably) malicious robots trying to gain access to WordPress config files.
However, not all the non-human requests were malicious. Some were being carried out by organisations looking for websites that might host restricted, copyrighted, or otherwise illegal content. Some of these organisations presumably work with legal authorities to report content.
Day 6 - Sunday June 8th 2025 (More Security, New Features)
Security - Token Validation
A key issue pointed out by Perplexity was that I was trusting the tokens sent by the client to my server. This meant that the token could contain modified information which would allow malicious users to potentially Yeet as someone else.
The solution was implementing backend token verification.

Google offered a library which allowed you to pass in a token and it would then check for the validity of the token. This is ideal as we cannot always trust the token sent to us, but we can trust the response that Google provides to us.
New Features - Delete Yeets
This feature was to be my ‘final test’ of sorts, to implement a feature into an application which had an existing architecture. I won’t include screenshots of my conversations with Perplexity but there was a lot of back and forth and a lot of rolling back code that didn’t work the first time.
The reason it was hard to implement was that it required a number of things to happen:
A user had to be logged in.
The user could only see a context menu which enabled deletion for Yeets they had posted.
Addition of a new API which handled Yeet delete requests.
When a Yeet was deleted, it had to be marked as such in my database (I’m sorry to break it to you, deleted things are not actually deleted).
Update the code which loads Yeets to only load non-deleted Yeets (I actually did this change myself).
Invalidate the Yeet cache because I did not want any new visitors, or anyone who reloaded the page to potentially see deleted Yeets.
Although I was able to implement the feature, it pointed out a key issue.
Models sometimes skip steps, or forget context that they have access to. It results in logic which you cannot always follow unless you know how to trace your code and figure out where the errors are coming from (through a combination of reading the code and using the browser console).
You can also consider this in terms of percentages. If there is a 10% chance that the response a model gives contains an error, then over the course of 10, 100, or 1000 prompts, you end up with an increasing amount of errors. Each one adds technical debt which requires untangling that not even the models can achieve.
Day 7 - Monday June 9th 2025 (Reflection & Reflection)
There was no code changes made on this day. I had achieved what I had set out to do, and I was pleased with what I had made and what I had learnt.
Primarily I was surprised at how quick I was able to get a prototype up and running, from simple authentication to developing what could be considered a MVP (Minimum Viable Product).
It wasn’t fancy, it didn’t have nice animations or bright colours, but it worked, and that was the goal I set out to achieve. If I had fretted over how it looked rather how it performed, I would probably still be figuring out a colour palette.
I am definitely convinced at how effective AI-tools can be used to help not only learn, but to streamline to process of creating things. They are far from perfect and they do make mistakes, but as long as you remain conscious of that, you can learn mitigate much of the risks.