TLDR
I built an open-source API called Readable Regex that lets you do common string manipulation tasks (like validating emails or extracting numbers) with simple API calls, and with no complex regex required! Gemini helped me generate a lot of the code (a lot of which I didn’t want to write yet again from scratch), handle tricky stuff like URL encoding, and even write this article. It was a super cool experience that showed me how AI can make coding faster, easier, and more fun. Check out the code on GitHub: https://github.com/drewg2009/readableRegex and try out the demo here.
(PS: Gemini helped write this article, so give it a high-five! 🖐️)
Ok so you want to read how I did it? Well let’s begin :D
The Challenge With Regex
While working at my company, I faced the problem of updating legacy code using regex expressions that were difficult to understand and that I didn’t write. The code was acquired from another company and I didn’t have any experience with it or how it was written, which is always challenging.
The code is a series of repositories orchestrating an Extract Load and Transform of client data from marketing platforms such as Shopify, Bigcommerce, and Magento. The code moves the raw data into audiences our company’s marketers can use to activate and drive higher ROI from segmented cohorts. In the transformation step, many regex expressions replace and validate string data before inserting it into a database.
While debugging our updates of the code, it was challenging to understand what the regex expressions were doing. In the age of AI, I could have easily popped some regex expressions into a tool like ChatGPT or Gemini to describe and alter them to fit my needs. However, this would only solve the problem in one repo at a time among a distributed set of repositories for this system. This would also be tedious and not solve the broader problem at hand.
Although Regex is a powerful language to validate and parse data, it contributes to less readable codebases, cluttered with comments to describe ciphers that make code less maintainable.
When code is less maintainable, it’s harder to understand, update, and improve on. This can slow team velocity and increase the risk of introducing bugs by incorrectly updating existing code.
An Idea to Improve Regex
While I was writing this code I thought of an idea. What if instead of writing regex expressions within the code, it could be abstracted into readable functions like you would with large blocks of code within a codebase?
Now the code already had functions for each of the regex transformations and validations and this wasn’t a groundbreaking idea by any means. But then I thought what if all of the regex functions you would ever need were in a library you could call readable functions from?
For example, you could call a function named onlyNumbers with an input string like $123abc
and get 123
as an output. Imagine using no regex at all and writing code that easily converts and validates your data. So instead of the code reading like this:
// validate input is number using regex
const inputString = document.querySelector("#input").value
const onlyNumbers = str.replace(/[^0-9]/g, '');
it could look like this:
const inputString = document.querySelector("#input").value
const onlyNumbers = onlyNumbers(inputString)
Now that might seem trivial and it wouldn’t make a difference because it's only a small part of one line that’s changing.
But imagine a codebase or multiple codebases where you have many values you are validating from forms, API calls, webhooks, etc that use regex to validate and transform data. What if you could replace all of those calls (which are hard to read) and comments (which become out of date really quickly when updates are made) with readable, plain English code that tells you exactly what it's doing?
Well, that was my idea! Now is this original? Probably not. There are tons of regex and string libraries out there that validate and transform values. I didn’t scour the internet for every possible solution, but it seemed like there wasn’t something exactly like this in plain sight so I thought I’d give it a shot.
Most of the time these libraries are specific to a language or framework which makes them not super extensible. So I wanted to take this a step further and make it more widely available.
What if the ‘library’ was an API that could be accessed via any language, framework, or platform that could make web requests?
Now all a sudden this little library could be used anywhere. So imagine rewriting all of your repos and distributed systems to use a singular API that makes all of them more readable, maintainable, and regex-free?
At this point this seemed like a more convincing solution (at least to me). As a Senior Software Engineer and Tutor who constantly jumps between different stacks, codebases, and frameworks, it can be challenging to keep up with every nuance (like regex or string libraries).
Although regex expressions are platform agnostic they still can be written different ways to achieve the same goal. How many times have you searched up ‘regex for validating email’ and you get a StackOverflow post with dozens of answers, that all work?
Every time I’ve been in this situation, I have had a tough time picking the ‘right’ solution and finding myself testing combinations of strings over and over until it seemed like it was correct (only to find out it covered only most of the cases).
In the age of AI and productivity, I think it’s also time to adapt Regex to be more readable and easier to maintain. Let’s take the pain out of data validation and transformation. Let’s make that seamless so we can focus on the more important problems at hand.
Formulating the Solution
The specific solution I thought of was to create an Express JS API that exposed HTTP routes to interface with to perform operations that regex would normally be used for. For instance, validating emails and transforming strings with different types of symbols to only numbers or letters. There are so many more cases, but I wanted to solve the simple problems and validate the solution quickly. My favorite and most used language is Javascript and I like how fast I can build with Express JS (because it doesn’t require a lot of coding or tooling to get set up).
I remember my cofounder and I building an Android smart alarm app called Clockwise in college and it took around four months each time we built a new version. It was hard to validate if the product was a good market fit, if there was money to be made from it, and what we needed to improve on with our release cycle. We were building and releasing, but not as fast as we could or should have to improve as much and quickly as possible.
Back then I was an avid builder, student, and young entrepreneur in the tech space. I felt I had more time to spend on building without sacrificing other things I loved. That was my only dream at the time. I wanted to follow in the footsteps of the greats like Zuck and Jobs, changing the world in the process.
But now I approach life in a much different way. I have so many different interests now. I want to improve my health, fitness, write and produce music, master marketing my music and other ventures, finally publish the book I wrote as a 16-year-old, travel, spend quality with my wife, advance in my software engineering career, and spend more time with friends experiencing fun things in life.
It seems like you only get one shot at life, so I want to make the most of it. To do that I want to try things quickly, validate them, and improve quickly or move on and learn something from it.
So all that boils down to me building an API in a couple days with a few functions and asking other people “hey, is this thing worth investing any more time in?”
So that’s what I did.
How Will I Build It?
Given we live in a very accessible AI era, I thought “why not just use AI to build all the stuff I would normally build by hand but it could do much faster?”
So I called my buddy Google Gemini (or messaged rather) and we agreed to work together on this app I called Readble Regex (which may be taken already, but that’s okay for now!)
I already knew Javascript, Express JS routing, Heroku Deployment, frontend templating with Pug, encoding, CSS, NPM, JSON, REST, and HTML so this wasn’t going to be my first rodeo.
And I think that’s important to note. I don’t believe that asking something like Gemini for all the answers and copying them into a solution is the best approach if you don’t understand the underlying concepts.
Ok… so if you ask Gemini to build a whole app, sure it could probably do it ( and I’m going to show you what it did for me :D). But what if you need to change something that Gemini can’t figure out right away or that you want to understand and manually update later? Well, you wouldn’t be able to. And it would be hard to get the result I obtained with AI without prompting for the specifics as quickly as I could. I don’t think anyone should cheat off of AI too much, but you also shouldn’t bang your head against a wall over little things 404 errors on routes (because you misspelled the route on the client side) or “why is my API not authenticated when I pass the token (because the API URL was wrong to begin with)”. It’s just extra time we waste, causing stress and ultimately taking away from your one shot at living a full life. You also want to make sure you are using your brain and learning with the AI. Otherwise, you will stop thinking as much and lose the skillsets you have worked hard for.
Simple roadblocks like 404 errors or syntax issues don’t define you as an engineer. If you understand DFS, model training, or binary search but can’t get the syntax exactly right from memory or you forget to use a pointer correctly, just check your notes, that’s what all the greats have ever done in any field. Our brains only have so much capacity to efficiently be used. It just so happens now the notes come from an AI Robot.
Ok so that’s a lot of writing so far without presenting the solution, so let’s get to it!
Build Time!
To preface, I felt super resourceful, so the remainder of this article is a collab between my buddy Google Gemini and I. Gemini helped me write a lot of repetitive code I didn’t care to write again as well as the documentation and content of this article. So you can credit us both for this one if you find it valuable.
The Solution: An API-Driven Approach
My solution was to create a simple API that would encapsulate the regex logic. This would allow clients to interact with the API using clear function calls, abstracting away the underlying regex complexity. This is where Gemini became my secret weapon.
My Gemini-Powered Development Journey: Step-by-Step
Laying the Foundation (Initial Prompt): I started with a broad prompt to get the ball rolling: can you generate me an express js API that has routes for calling regex functions to remove all non-numeric characters from a string, and remove all non-letters from a string?
Gemini responded with a basic Express.js structure, including the necessary routes and placeholder functions. It wasn’t perfect, but it gave me a solid foundation to build upon. I used Express.js which is a fast, unopinionated, minimalist web framework for Node.js. You can find the documentation here.
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
// Function to remove all non-numeric characters
function removeNonNumeric(str) {
return str.replace(/[^0-9]/g, '');
}
// Function to remove all non-letter characters (including spaces and punctuation)
function removeNonLetters(str) {
return str.replace(/[^a-zA-Z]/g, '');
}
// POST route to remove non-numeric characters
app.post('/api/removeNonNumeric', (req, res) => {
const { inputString } = req.body;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required in the request body.' });
}
const result = removeNonNumeric(inputString);
res.json({ result });
});
// POST route to remove non-letter characters
app.post('/api/removeNonLetters', (req, res) => {
const { inputString } = req.body;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required in the request body.' });
}
const result = removeNonLetters(inputString);
res.json({ result });
});
// Example using query parameters (GET requests)
app.get('/api/removeNonNumeric', (req, res) => {
const inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = removeNonNumeric(inputString);
res.json({ result });
});
app.get('/api/removeNonLetters', (req, res) => {
const inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = removeNonLetters(inputString);
res.json({ result });
});
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
Gemini generated this code in seconds, which would have taken me at least an hour or more to write correctly if any weird issues came up. It saved me from writing this same code I have written hundreds of times in POC’s and large projects. It was time to hand the keys to Gemini to write simple API routes.
I copied the code and put it into a project I created locally. I created a folder called readableRegex , ran npm init to configure the Node JS project, created an entry point called index.js, and put all the code in there for the initial routing and regex functions as well as the express setup for the server. I also created a public repo on Github and pushed the source code there.
Building the Regex, Iteratively (with Gemini):
I followed up with several prompts to remove POST requests for now, handle other common cases for validation purposes, rename functions and routes to make them easier to use, and document code which generated something like this:
const express = require('express');
const app = express();
const port = process.env.PORT || 3000;
// Middleware to parse JSON request bodies
app.use(express.json());
app.set('view engine', 'pug')
const router = express.Router()
const decodeErrorMessage = 'Invalid input string. Could not decode URI component.'
// Function to remove all non-numeric characters
function onlyNumbers(str) {
return str.replace(/[^0-9]/g, '');
}
// Function to remove all non-letter characters (including spaces and punctuation)
function onlyLetters(str) {
return str.replace(/[^a-zA-Z]/g, '');
}
function onlySpecialCharacters(str) {
return str.replace(/[a-zA-Z0-9\s]/g, ''); // Keep only special characters
}
function getDecodedInputString(inputString) {
// Decode the URI component to handle special characters
try {
inputString = decodeURIComponent(inputString);
} catch (error) {
return decodeErrorMessage
}
return inputString
}
function getDecodedInputStringErrResponse() {
return res.status(400).json({ error: decodeErrorMessage })
}
function isEmailAddress(str) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(str);
}
function isPhoneNumber(str) {
// A basic phone number regex (you might need to adjust it for your specific needs)
const phoneRegex = /^[+]?[(]?[0-9]{3}[)]?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4}$/im;
return phoneRegex.test(str);
}
// GET routes for isEmailAddress and isPhoneNumber
app.get('/api/isEmailAddress', (req, res) => {
let inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = isEmailAddress(inputString);
res.json({ result });
});
app.get('/api/isPhoneNumber', (req, res) => {
let inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = isPhoneNumber(inputString);
res.json({ result });
});
// GET route for onlySpecialCharacters
app.get('/api/onlySpecialCharacters', (req, res) => {
let inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = onlySpecialCharacters(inputString);
res.json({ result });
});
// Example using query parameters (GET requests)
app.get('/api/onlyNumbers', (req, res) => {
const inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = onlyNumbers(inputString);
res.json({ result });
});
app.get('/api/onlyLetters', (req, res) => {
const inputString = req.query.inputString;
if (!inputString) {
return res.status(400).json({ error: 'Input string is required as a query parameter.' });
}
const result = onlyLetters(inputString);
res.json({ result });
});
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
The Special Character Challenge (Prompt Refinement): I quickly realized that simply passing the input string directly in the query parameter wouldn’t work for special characters. They needed to be URL-encoded. Otherwise, the requests wouldn’t pass the special characters (e.g. = or #) through that would be escaped or reserved for other purposes.
I discussed possible workaround or shortcuts with Gemini to avoid encoding/decoding but we weren’t able to find a solution.
I went back to Gemini with a more specific prompt: Update the previous Express.js code to correctly handle special characters in the query parameters, like #, &, and others. Use decodeURIComponent() where necessary.
Gemini’s response was spot on. It correctly added the decodeURIComponent() function call within the route handlers, ensuring that special characters were properly decoded before being passed to my regex functions. I just had to encode the query parameter inputString on the frontend script.js file so that it came in encoded in the right format. The cool thing is Gemini even generated error handling code for if the decoding call failed, which was super helpful.
I quickly saw that this code was going to be repetitive in each route for decoding the input parameter. I tried to write a function to handle this but it didn’t allow me to return an error response back to the client without returning the function response for both success and failure, which was not what I wanted and wouldn’t work in this case.
I remembered I could add middleware to modify the request before it hit the route, decoding the value and handling the errors there and centralizing the functionality. I did this by myself as as learning experience and it was pretty simple with express docs.
I also asked Gemini to create a readme for me with the following prompt: Create a README file for the Express.js API. Include information about the API endpoints, how to use them, and the benefits of using this approach.
Gemini provided a good starting point for my README which I then customized with more specific details and examples.
I tried to get Gemini to write a Medium-based article for this project and add visuals. Although it generated a great summary in this article, I got this message about visuals:
(A Note on Visuals: While I’d love to add diagrams, code snippets, and other visuals to this article to illustrate the process and make it more engaging, as a large language model, I currently don’t have the capability to directly add images to online platforms like Medium. I can, however, strongly recommend places where you could add images and what those images could be. I hope that this limitation will be addressed in the future.)
Although slightly disappointed, visuals are the fun part so I was happy to do that.
I manually deployed to Heroku on a dyno and used Github to host the source code. I only ran into one minor hiccup with the deployment. I was missing a npm "start" script in the package.json file. I added it start: node index.js so it would run the app on deployment.
I built the frontend scripts and markup with Pug, CSS, HTML, and JS to interface with the API from a sample client-side frontend. I built from what I knew as quickly as possible but then I ended up with this (I’m not a designer btw):
* {
font-family: Arial, Helvetica, sans-serif;
}
body {
padding: 10px;
}
#responseBox {
background: gainsboro;
width: auto;
/* display: inline; */
padding: 30px;
width: 200px;
}
Kind of boring and hard to read right?
So I thought, hmm… what if Gemini could write some styles for me?
So I gave it a shot and asked based on this pug file below, could you write some css that makes this site look more readable?
Here’s what it turned out to be on the first and only try (for now):
body {
font-family: sans-serif;
margin: 20px;
background-color: #f8f8f8; /* Light gray background */
color: #333; /* Dark gray text color */
}
h1, h4 {
text-align: center; /* Center the headings */
margin-bottom: 10px;
}
label {
display: block; /* Make labels stack on top of inputs */
margin-bottom: 5px;
}
input[type="text"], select {
width: 300px;
padding: 8px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box; /* Include padding in width */
}
button {
padding: 10px 20px;
background-color: #4CAF50; /* Green */
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:disabled {
background-color: #ddd; /* Gray when disabled */
cursor: default;
}
#responseBox {
border: 1px solid #ccc;
padding: 10px;
margin-bottom: 20px;
background-color: white;
border-radius: 4px;
min-height: 50px; /* Ensure some height even when empty */
}
pre {
background-color: #eee; /* Light gray for code samples */
padding: 10px;
border-radius: 4px;
overflow-x: auto; /* Add horizontal scroll if needed */
white-space: pre-wrap; /* Allow code to wrap */
}
/* Responsive adjustments (example) */
@media (max-width: 400px) {
input[type="text"], select {
width: 90%; /* Make inputs full width on smaller screens */
}
}
/* Add some hover effects */
button:hover {
opacity: 0.8;
}
input[type="text"]:focus, select:focus {
border-color: #4CAF50; /* Green on focus */
outline: none;
box-shadow: 0 0 5px rgba(76, 175, 80, 0.3); /* Green glow on focus */
}
/* Style the code samples area */
pre {
max-height: 300px; /* Limit the height of code samples */
overflow-y: auto; /* Add vertical scroll if needed */
}
Wow…not bad Gemini!
Yes a little bland still, but hey! Miles ahead from my short attempt at the minimal amount of CSS. Don’t get me wrong, CSS is fun and taps into my creative brain but it’s just tedious sometimes and Gemini did a better job anyway (especially with the scrollable code sample that I hadn’t done before).
Testing…123
It was time to test the API.
Instead of writing a long blurb about that, essentially I used the dropdown I created on the frontend to select different API endpoints. Then the button would call the API with that endpoint and load in the response that was transformed.
Here is a demo video of using a client-side app and API to transform/validate values using Readable Regex. This is the current version of the API at the time of writing this article so this functionality is subject to substantial change in the future.
https://www.loom.com/share/fcbfcfd9442e47bab169eab025049194?sid=6042408b-ebfc-446d-bfcd-e49995e7445b
The Outcome: A Clean and Maintainable API (Open Source!)
By working iteratively with Gemini, I was able to build a clean, readable, and maintainable API. The complex regex logic is now hidden behind simple function calls, making the code much easier to understand and modify. If I ever need to change the regex or add new functionality, I can do so without fear of breaking other parts of the code. And now, this project is open-source! You can find the code on GitHub: https://github.com/drewg2009/readableRegex. Feel free to explore, contribute, and use it in your own projects.
There is a client-side demo of the API (hosted on the same deployment as the public API). No API key yet, and if this project is worth valuable time, I will continue to contribute to it, hopefully along with the help of Gemini and an open-source community.
Here is the base URL for the API.
Here is a client-side demo app you can inspect the source of and test out here.
Lessons Learned:
Gemini as a Force Multiplier: Gemini significantly accelerated my development process. It handled the boilerplate code and helped me solve specific challenges, allowing me to focus on the core logic. By using my past experience, I was able to efficiently prompt Gemini to generate the code I needed, saving me time on repetitive code like routes and validation functions and allowing me to focus on new concepts like middleware and some frontend development.
The Power of Iterative Prompting: The iterative process of prompting Gemini, reviewing the code, and refining my prompts was key to achieving the desired result.
Code Readability is King: Encapsulating complex logic behind clear function calls is essential for writing maintainable code. Making this code open-source allows others to benefit from this approach as well.
Despite only working on this for like 48 hours (a couple hours each day, thank you Gemini), I did learn a lot.
I learned how to:
Use middleware to extract repetitive code and transform input to benefit all routes.
Create a public-facing API that can benefit other engineers and myself in the process
Validate an idea quickly from scratch
Use AI to drastically speed up my workflow
Pros/Cons Of This Approach
From the start, I knew that this idea might have some flaws as well as some clear benefits:
Pros
Save time writing regex expressions and trying to understand them
Refactor code agnostic to platform, language or framework to use HTTP calls have easier to maintain regex-based code
Defer the maintenance for regex validation from to an open-source community of passionate developers who can work together to make the best solution possible
Cons
Another API dependency that could fail under load and prevent your app from working if not properly developed
May not contain every regex function when developed further, limiting usage
Apps may experience latency and require more async code on the frontend to manage multiple requests (could be solved easily by allowing for larger payloads of multiple input bodies and their associated regex calls or by converting the API to language-specific libraries that don’t require network calls)
Whenever developing any solution, the important thing is to call out these observations to improve the product quickly regardless of its lifetime or usage. It’s one of the key strengths of any engineer (even with a lot of help from AI).
Future Plans
If the developer community finds this as valuable as I do, I will likely contribute to it as an open-source project and invite others to do so as well.
I know the concept isn’t rocket science and that’s kind of the fun and beauty of it.
Building small projects like these that solve obvious problems appeal to a lot of people and quickly enable them to iterate on it and solve the bigger problems at scale.
Who knows, maybe there’s an AI component that could be incorporated to predict a regex expression transformation/validation based on the input value (maybe even something super complex like the contents of a csv or word doc or even code)
Here are some potential enhancements I would love to add at some point if this project is worth developing further:
many more regex validations/transformation routes
rate limiting to prevent server resource exhaustion
caching for faster responses
API keys to prevent malicious usage
swagger docs for easier API usage
library code generation for languages like python, java, and PHP (similar to how Google generates their SDK libraries from a REST API).
some form of AI component (for learning purposes) like predictive validations/transformations based on the input value and input context (email, document, number, use case). Maybe even an LLM integration on the API client side to generate new rules and open PR’s without developer intervention
scaling capabilities using load balancer or Heroku solutions
more code organization to organize the routes and functions
more complex validations that may even go outside the realm of regex
Thoughts on AI
To be transparent I have had reservations for a while to hand my creative and engineering keys over to AI. Not because I don’t think it’s useful, but more so because I fear what it might do to me.
I fear it might replace my current role in the future, making me feel like all my hard work to learn software engineering was a waste of time and energy. I thought it would make me a less intelligent developer because I would rely on it too much so I would become useless from not actively thinking through problems and solving them myself.
But the truth is that regardless what the future holds for AI and software engineering, it’s all about how you use the technology and what you can add to it that makes or breaks your successful adoption of it.
I wouldn’t use AI to build something I don’t at least partially understand or can ask it to teach me. I would leverage it to learn much faster because of it’s ability to predict the right response to my questions from its aggregation and analysis of mass amounts of data. I think if we can learn with AI we can accelerate our growth and not fear being replaced by it.
We have to remember, AI is trained on everything humans have done, it’s our data. So really we should pat ourselves on the back if we can make it this smart!
Now it’s up to us to use it responsibly for good and to improve our lives while still developing ourselves as avid learners just like it is.
This project made me appreciate AI a lot more than I already started to. It made me see how much time in my life I could take back control of, and that’s inspiring.
We all want to do things outside of work and entrepreneurship. We desire love, creativity, fun, adventure, mystery, and human connection. This will allow us to do that faster than ever before.
That’s my TED talk. Thank you for reading if you made it this far haha.
PS: Gemini came up with the article title, not bad right?
Feel free to contact me about this project or others. Always open to discussion of new ideas and collaborative efforts.
https://www.linkedin.com/in/drew-gallagher-945417a1/
https://www.producthunt.com/@drewg2009
drewg2009@gmail.com
Top comments (0)