In-Depth
Hands-On Comparison: Building a Dynamic Web App in VS Code and Google Antigravity with Prompts Only
Recently, I've been running hands-on tests of AI-assisted coding environments to see how well they handle full "text-to-website" generation using prompts as the primary interface, using Visual Studio Code as the baseline editor experience. In earlier articles, I compared tools such as Cursor and Windsurf against VS Code by giving them similar instructions and then evaluating not just what they generated, but how well the editors supported iteration, debugging and correction once the first draft of code was on disk (see "Hands On: Testing Cursor, Windsurf and VS Code on Text-to-Website Generation").
A follow-up comparison added Google Antigravity to the mix and highlighted how quickly VS Code forks are diverging in workflow and philosophy, even when they share familiar UI elements (see "What a Difference a VS Code Fork Makes: Antigravity, Cursor and Windsurf Compared").
This article extends that line of inquiry by escalating the scope from static website output to a simple dynamic web application proof-of-concept: a lightweight Ticket Desk application with server-side rendering, session-based admin access and JSON-backed persistence. The goal is to examine not only whether an editor can generate working code from prompts, but how it behaves during validation, troubleshooting and refactoring under real constraints. Just as important, the comparison surfaced differences in workflow: VS Code behaved like a prompt-assisted editor, while Antigravity behaved more like an agent-driven environment that imposes a planning and approval process around code generation and execution.
For this comparison I focused solely on Google Antigravity as the second environment alongside VS Code, in part because it represents a different approach to AI-assisted development: a more agent-driven workflow layered on top of a VS Code-like editor experience. Antigravity is a more recent entry in the AI-assisted coding space, formally announced in late 2025 as a new "agent-first" integrated development environment that can orchestrate autonomous coding, testing and validation tasks alongside traditional editor functions.
It has generated significant developer interest due to its backing by Google, its narrative of evolving beyond simple code completion, and its availability, which appeared to offer relatively generous usage limits compared with some competing tools, such as Cursor, whose free tiers can hit usage caps quickly during larger projects and thus locked up on me in an earlier test.
You can see both sites in action thanks to my free hosting on Netlify (for VS Code) and Replit (for Antigravity), which involved many more steps including setting up a GitHub repository, configuring environment variables, and adapting the Express server for the hosting environments.
[Click on image for larger view.] Ticket Desk Home Page Generated in the VS Code Run. (source: Ramel).
[Click on image for larger view.] Ticket Desk Home Page Generated in the Antigravity Run. (source: Ramel).
Methodology
The test used a single dynamic proof-of-concept application called "Ticket Desk." The stack was intentionally simple: Node.js with Express and EJS for server-side rendering, and a JSON file used to simulate database persistence. The app provided public pages for ticket submission and viewing, plus an admin login and dashboard protected by a session. Tickets were stored on disk in a JSON file so that data would persist across server restarts.
Constraints were kept consistent across both runs. The application code was generated by the tools, not manually written. Dependencies were kept minimal and installed via standard npm commands. Sensitive configuration was required to come from environment variables, with the app expected to fail clearly if required configuration was missing. The validation steps included persistence across restarts, admin-only behavior for protected routes, and an API endpoint that returned JSON data for authenticated admin sessions.
The main prompts used are presented at the end of the article for reference.
Dynamic App Requirements
The Ticket Desk app included public routes for home, ticket submission, and listing tickets, plus admin routes for login and a protected dashboard. Admin actions included updating a ticket status and deleting tickets. The test also included an admin-only API endpoint that returns tickets as JSON. Persistence was implemented using a JSON file on disk so that submitted tickets would remain after the server was stopped and restarted.
VS Code Run
In the Visual Studio Code run, the initial app generation produced a working Ticket Desk implementation with server-side pages and JSON-backed persistence. The app could accept tickets, render them in the UI, and preserve them across restarts. Admin functionality was available through an authenticated session, including status updates and deletions. The admin-only API endpoint returned JSON for authenticated sessions and redirected to the admin login page when not authenticated.
[Click on image for larger view.] VS Code Run: Admin Dashboard for Managing Tickets (source: Ramel).
During early setup, a missing dependency error surfaced when the server was started without first installing packages. After installing dependencies, the app was able to start successfully. The configuration handling was also tightened during the run to require an admin password via environment variable and to fail at startup when the variable was missing.
Antigravity Run
The Google Antigravity run introduced a different workflow shape at the outset. It entered a planning and task phase before generating code and moving into execution steps. Antigravity also presented multiple UI approval prompts during the run for file changes and command execution, requiring repeated user confirmation.
In one setup step, Antigravity also reported an error applying a file change ("Model produced a malformed edit that the agent was unable to apply"), requiring the environment configuration to be completed manually. That kind of failure mode was distinct from the VS Code run, where issues were primarily conventional dependency and configuration errors.
In the Antigravity run, the dynamic baseline behavior was validated: a submitted ticket persisted across server restarts. Admin login and dashboard behavior also worked, and the API endpoint returned JSON after logging in. However, runtime verification uncovered issues that required follow-up prompts to align behavior with the stated requirements. In one case, the API route was mounted without admin protection, allowing access to the ticket JSON without logging in. That was corrected by applying the existing admin authentication middleware to the API mount, resulting in redirect-to-login behavior when not authenticated and JSON output when authenticated. A separate redirect-path issue was also observed during testing and then did not reproduce after a controlled logout and retest flow.
Validation Results
Across both runs, the baseline dynamic validations focused on observable behavior rather than code inspection alone. Tickets persisted across restarts. Admin login protected the dashboard. Ticket status updates and deletions were reflected in the UI. The API endpoint returned JSON when authenticated and redirected to login when not authenticated. These checks established functional parity before moving on to the refactor step that tends to expose larger workflow differences.
Antigravity Run: Public Ticket Listing Displayed in a Card-Based Layout (source: Ramel).
Non-Trivial Refactor: JSON Persistence Hardening
After the apps were running, both workflows were prompted to harden the JSON persistence layer without changing routes, URLs, or user-visible behavior beyond error handling. The refactor requirements included atomic file writes using a temp-file-and-rename strategy, protection against concurrent write corruption using an in-process queue or mutex, auto-initialization of a missing data file, and a friendly error page when the JSON data file is corrupt or unreadable. The key constraint was to preserve existing behavior while improving reliability and error handling.
In the VS Code run, the refactor produced a dedicated persistence module that implemented atomic writes and a simple serialized write queue. It also introduced a friendly error view and route-level handling to surface corrupt data conditions. The resulting implementation preserved the public and admin flows under normal conditions and provided a clear failure mode when the data file was invalid.
Here is VS Code's summary of changes made after a prompt to upgrade JSON persistence:
Summary of Changes
Files Changed:
ticketStore.js (NEW) - Ticket persistence module with atomic writes
error.ejs (NEW) - Friendly error page for corrupt data files
server.js (MODIFIED) - Updated to use ticketStore module
Key Improvements:
✅ Atomic writes - Uses temporary file + rename pattern to prevent data corruption during writes
✅ Concurrent write protection - Implements in-process promise queue to serialize all write operations
✅ Better error handling - Corrupt JSON files now show a friendly error page with the file location and fix instructions (no silent failures)
✅ Auto-initialization - Creates tickets.json with [] if missing
✅ No new dependencies - Uses only Node.js built-in fs and path modules
✅ Caching - Keeps tickets in memory for faster reads
✅ Clean API - Exported functions: getTickets(), addTicket(ticket), updateTicket(id, updates), deleteTicket(id)
Behavior preserved:
All routes, URLs, and UI remain unchanged
Only error handling improved with friendly error page
In the Antigravity run, the persistence refactor similarly introduced a new ticket store module with atomic writes and a serialized operation queue. It also introduced an error view and updated routes to handle a specific corruption error code. A follow-up prompt was required to align the API route behavior with the requirement to render the friendly error page for corrupt data, rather than returning a JSON error payload. After that adjustment, the API endpoint returned JSON normally when authenticated and redirected to login when not authenticated, while also being able to render the friendly error page in the corruption scenario.
Workflow Differences Observed ('the guidance from the user is what drives me')
Several workflow differences emerged from the hands-on process. Antigravity employed an explicit planning/task phase before implementation and relied on repeated UI confirmations for file and command operations, similar in friction level to the approvals encountered in the VS Code workflow. Antigravity also surfaced internal tradeoffs in at least one correction step (API redirect behavior versus a 401-style API response), then proceeded with the user-directed requirement to preserve consistency with existing admin middleware behavior.
Here's an example of Antigravity's "thinking" process during the refactor phase:
I've reevaluated the redirect strategy. While a 401 response might seem preferable for an API, the task guidance specifically mentions protecting /api with the existing admin middleware. The existing middleware redirects to /admin/login. Keeping the /api implementation consistent with the existing /admin behavior is crucial to follow the requirements. Therefore, I will stick to returning a 302 redirect. I'll maintain this approach, even if it seems suboptimal for an API, because the guidance from the user is what drives me.
In both environments, the most meaningful differences appeared after the initial generation phase, when corrections and refactors were required to preserve behavior under constraints.
Conclusion
This dynamic Ticket Desk exercise reinforced a key lesson from earlier static-site tests: initial code generation is only the first step. The more meaningful comparison emerged once the app was running and the work shifted to validation, troubleshooting and refactoring under constraints. Both environments were able to generate a functional baseline that met the core requirements, including persistence across restarts, session-protected admin routes and an authenticated API endpoint.
The differences became clearer in how each environment handled the process. The VS Code run behaved like a familiar prompt-assisted editor workflow, with issues that largely resembled conventional setup and configuration steps. Antigravity, by contrast, introduced a more agent-driven workflow with an explicit planning phase and repeated approval prompts for file changes and command execution, along with at least one agent-level failure mode that required manual intervention. For readers evaluating these tools, the takeaway is that the choice is not only about what gets generated, but about how much control and friction the environment introduces once the project moves from scaffolding into real-world iteration.
Prompts Used
Here are the main prompts used during both runs for reference. These include some directions to me from a ChatGPT chat, which guided me through this project:
-
Prompt 0 (Constraints + Overview)
Paste this first:
"Build a minimal dynamic website PoC called ‘Ticket Desk' using Node.js + Express + EJS. Requirements:
Public pages: / (home), /submit (ticket form), /tickets (list tickets).
Persistence: tickets stored in data/tickets.json and persist across server restarts.
Admin: /admin/login + /admin dashboard protected by session. Use env var ADMIN_PASSWORD for login.
Admin can update ticket status (Open/In Progress/Closed) and delete tickets.
Provide at least one JSON API endpoint: GET /api/tickets (admin-only is fine).
Constraints:
Keep dependencies minimal: express, ejs, and ONE session library max.
No TypeScript, no React, no database/ORM, no CSS frameworks.
Use only server-side rendering (EJS) for pages.
Deliverables:
Exact file tree
Full contents of every file
Exact commands to run locally
A .env.example
A README with test steps."
Success check: it responds with a plan + file tree + code + commands.
-
Prompt 1 (Scaffold + Run)
After it provides code, run the commands. If it didn't include them, use:
"Now give exact commands to initialize the project, install dependencies, and run it. Include expected output and which URL to open. Ensure npm run dev exists."
You'll likely end up with:
npm init -y
install deps
scripts added in package.json
-
Prompt 2 (JSON "DB" Module Quality)
Once it runs and renders pages (even if tickets don't persist yet), force the persistence layer to be clean:
"Implement a JSON-file persistence module at src/lib/ticketStore.js with:
getTickets()
addTicket(ticket)
updateTicket(id, updates)
deleteTicket(id)
Requirements:
Store file at data/tickets.json
If missing, create it with []
Use atomic writes (write temp file then rename) to avoid corruption
Prevent concurrent writes with a simple in-process queue/mutex
Update routes to use this module."
Success check: submit writes to JSON, list reads from JSON.
-
Prompt 3 (Validation + UX)
"Add server-side validation for submit: required fields (name, email, subject, description) and basic email format. On validation error, re-render the form with inline error messages and preserved field values. On success, redirect to /tickets with a one-time success message."
Success check: errors display; success message appears once.
-
Prompt 4 (Admin Login-Lite)
"Implement admin auth:
Route /admin/login shows a password form
On POST, compare to process.env.ADMIN_PASSWORD
If valid, create a session and redirect to /admin
Add /admin/logout
Protect /admin and admin actions; unauth users redirected to login
Keep it simple, secure defaults where possible."
Success check: admin protected; login works; logout clears session.
-
Prompt 5 (Admin Actions + API Endpoint)
"Implement admin dashboard actions:
List tickets with status dropdown and 'Save' button per ticket
Delete button per ticket with confirmation
Status values limited to: Open, In Progress, Closed
Add GET /api/tickets that returns JSON (admin-only)."
Success check: status updates persist in JSON; delete persists; API returns JSON when logged in.
-
Prompt 6 (Error Handling + Guardrails)
"Add guardrails:
If data/tickets.json is unreadable/corrupt JSON, show a friendly error page explaining the issue and where the file is.
Ensure templates escape user input (default EJS escaping is fine; do not use unescaped output).
Add a very lightweight rate limit to /submit (in-memory, per IP, e.g., 10/min) without external services."
Success check: corrupt file handled; rate limit behaves; no raw HTML injection.
-
Prompt 7 (README + Self-Test)
"Update README with:
Setup steps
Env vars
How to test public flow
How to test admin flow
How to verify persistence across server restarts
Then provide a 'Self-Test Checklist' confirming every requirement is met and listing all routes/endpoints."
Success check: README is runnable; checklist covers everything.