Ever since I realised I can use existing skills to launch my own software, my mission has been to build as many projects as possible. By doing so, I aim to improve my front-end skills and launch an app people will want to pay for.

But getting started was always a sticking point for me.

Although I had an idea for project #3, I didn’t yet have the enthusiasm to begin coding the first feature.

So I made a list of ways to boost my excitement for the project. Number one was a landing page—the first page someone sees when they visit my site.

Building the landing page in advance offered a window into the mind of a potential customer. Imagining all the ways this unbuilt app would be useful to them was fuel for me to get started.

So I decided to devote 7 days to building this next project—a channel banner generator for YouTubers.

Day 1: You have to start somewhere, so it might as well be here

The goal of this app is to help YouTubers avoid boring banners on their channel home page. By overlaying text on a banner image and automatically uploading it to YouTube, I hoped to create a more dynamic channel page to attract new viewers.

With this goal explained on my landing page, I set to work figuring out how to turn this idea into working software.

7 day plan

The plan for day #1 was to prove to myself it’s possible to overlay text on an image, both in the browser and on the server-side. To overcome my perfectionism, I told myself to build the quick and dirty solution.

So I asked ChatGPT for some suggestions.

How to overlay text onto an image in a way that works on both frontend (in browser) and on the backend (on server)?

This option seemed simple enough:

  • In the browser, use position: absolute to overlay text over an image.
  • On the backend, use the JavaScript canvas library to do the same.

Given prior experience with CSS positioning, I quickly built a Vue component to add text to an image.

Text overlay component

Moving onto the backend, I installed the canvas library, then wrote code to overlay an image in a similar way—outputting a PNG file.

When I ran the code locally, to my amazement it worked first time.

I ended the day on a high.

But I knew tomorrow wouldn’t be so straightforward, as I would be forced to implement a feature most developers dislike with a passion.

Day 2: The showstopper that jeopardises your entire project

Authentication with YouTube was the second item on my list of deliverables.

The user needs to setup a connection from my app to their YouTube channel. This way, my app can call the YouTube API on their behalf to publish a new channel banner.

The downside of authentication with YouTube is you’re forced to use a fiddly technology called OAuth 2—one that’s brought many developers to tears.

But after creating an OAuth client in Google Cloud Console (which manages YouTube API access) and asking ChatGPT for the appropriate code, I had successfully generated the access and refresh tokens necessary to make API calls.

I decided to finish the day by attempting to use the tokens I had generated to call the YouTube API and update my channel banner.

That’s when I ran into problems.

The YouTube API docs describe the property I needed as deprecated—meaning at some point in the future it might be removed entirely.

Deprecation notice

For an hour I thought about cancelling the entire project. After all, a YouTube banner tool isn’t much good if it can’t update the banner.

I considered the days of wasted effort that could have been avoided by simply checking the API docs first.

I gave myself one last shot at calling the API. With a request body that looked sensible, I hit run. Unsurprisingly, the function returned an error.

But when I refreshed my YouTube channel page, the banner was updated.

Update channel banner.

Despite the error, my channel banner had changed.

What the heck?

The sub-property I needed was in fact not deprecated. It was strange the API was returning an error, but I could always interpret that as success in code.

The game was on.

I had successfully called the YouTube API. Even though all this code was only running on my local machine, I felt like I had cracked the authentication problem.

The next day I would devote to moving that code to an actual API that plugs into the OAuth flow.

“Setting that up will be easy” I thought.

But I didn’t know about the surprise that was waiting for me thanks to a last-minute change in my tech stack.

Day 3: If development was easy, everyone would do it

Drawing a flow diagram is my way to visualise a potential solution and make sure it makes sense before bashing out the code.

I wanted to build YouTube authentication into the UI—for now a simple button to launch the user into the OAuth flow (the ‘Setup authentication’ step below).

Technical overview flow diagram

I believed this needed an API to generate an OAuth URL to which to forward the user.

“No problem” I thought.

I wrote a JavaScript function in the format expected by AWS Lambda—the serverless technology I use to run backend code on a pay-per-use basis to power my APIs.

export const handler = async (event) => {
    // function code goes here
}

After deploying the function, I tested it using the trusty command-line tool curl.

It didn’t work.

The code was missing the OAuth library it needed to call Google. I tracked the problem down to a recent upgrade to my deployment tool, from Serverless Framework v3 to v4.

The day took a downward turn as I spent hours searching for solutions and trying every suggestion. None of them worked.

As day turned into evening, I eventually found what I was looking for—an innocent one line change hidden deep in a GitHub issue

package:
  individually: true
  patterns:
    - package.json

Finally, the API was working.

All I felt at the time was frustration. Especially when I realised the code I thought belonged in an API could just run in the browser. At least what I’d learnt would be used to build other APIs later in the project.

To prove I hadn’t completely wasted my day, I quickly knocked out a green Link YouTube account button to initiate the authentication flow.

Maybe I would have more luck tomorrow.

Day 4: Churning out APIs like a machine

Today I’m exhausted from yesterday’s ordeal, so come up with an idea for a challenge.

Is it possible to build all the APIs for the authentication flow in a short time period?

I set a 60 minute timer and put the theory to the test.

With all the build issues fixed, I was ready to become a coding machine:

  • I wrote a callback API to complete the authentication flow—logging the generated tokens to the console.
  • I hooked the new API into the OAuth flow and got Google to successfully call it.
  • With 7 minutes to go, I decided to setup a database to store the tokens.

This was the DynamoDB table definition:

    UsersTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: ${param:dynamoDBTablePrefix}users
        BillingMode: PAY_PER_REQUEST
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH

Just as I was pushing the change to AWS, the alarm sounded. Time was up.

I let the deployment finish and tested the callback API again. I was amazed to see the database table contained the tokens I was expecting.

{
    "id": "fake-user-123",
    "tokens": {
        "accessToken": "access-token-123",
        "refreshToken": "refresh-token-123",
        "expiryDate": "1731422900530"
    }
}

Unfortunately, there were many tiny steps remaining with very little time to finish them.

Day 5: With the foundations in place, it’s time to build!

To complete my database setup, I needed real user ids to store the tokens against.

So creating a user and signing into my app was the goal for day #5.

I’d implemented this on previous projects, so copied the code to define the required resources in Cognito, AWS’s user management service. AWS provide a sign-in form, which I hooked up to eventually sign in to my site.

Cognito sign in form

This felt like real progress. With everything I’d learnt up to now, I had all the ingredients to build the UI.

The next task was creating this component to show a user’s linked YouTube channel.

Linked YouTube channel

This is the kind of feature you normally take for granted, but somehow it took me five days to reach this point. Still, it was a Sunday and it felt good to make progress rather than take a traditional ‘day of rest’.

After adding the option to unlink the YouTube account, the fiddly authentication work was behind me. But with only two days to go, my app was still unable to serve its main purpose—publishing a banner to YouTube.

I would need to implement that very soon to have any chance of meeting my deadline.

Day 6: Unexpected issues that eat up your day

I continued building the UI, adding a feature to automatically download the user’s current YouTube channel banner. By storing this in S3, AWS’s object storage service, I would be able to access it later.

Then I integrated the UI component that lets you overlay text on an image (the one I built on the first day). Next I had to replicate the same functionality, but on the backend.

I moved the backend code I had seen working locally to an AWS Lambda function. Remember this code uses the canvas library to overlay text on an image? Since AWS Lambda runs code in Node.js, I assumed it would work out-of-the-box.

Not true.

I hit a No such file or directory error, indicating a missing runtime library. Searching online, I found plenty of suggestions, but none of them worked. After hours trying to fix the issue, I admitted defeat and ended the day on a downer.

But tomorrow was my last day. Not only would I need to resolve this issue, but a countdown banner, payment, and many other tasks still remained.

Nothing short of a miracle would get all this finished on time.

Day 7: Too many tasks, too little time

A good night’s sleep helps attack a problem from a fresh angle.

I resolved my runtime issue by using a pre-built environment, known as a Lambda layer, tailored to the canvas library. With the runtime errors fixed, I could now add text to an image and publish to YouTube.

Suddenly, my target was within reach again.

But the list of remaining tasks was long:

  1. Generate countdown banner
  2. Use same font on client & server
  3. Mark multiple YouTube accounts feature as “coming soon”
  4. Automatically create account after Stripe payment
  5. Remove “create account” option
  6. Deploy to production
  7. Test in production

I built the countdown banner component at lighting speed, writing a function to store a schedule in the database, then hooking up an hourly task to dynamically update the banners in YouTube.

Before I knew it, it was 6 p.m.

I continued by hooking up the ‘buy’ buttons to Stripe checkout. Using a fake credit card, I could buy my product. But there were many bugs remaining and only a few hours to polish the app to an acceptable level.

That’s when I hit a major issue.

Markdown that was supposed to render as HTML was instead showing an error message on several pages. Although this didn’t effect core functionality, with midnight approaching I had to make a final decision.

Should I publicly release the app with these known errors?

I realised that doing so would be incredibly stupid, especially just before going to bed. Even if nobody visited my site, I would struggle to sleep having just sent an invite to try my sub-standard software product.

If that meant failing my 7-day goal, then so be it.

Day 7.5: The final hurdle

I woke up the next morning with a mission to get this project released.

The deadline had passed, but at least I hadn’t released confusing, buggy software for the sake of a self-imposed time limit. Although I’d lowered the bar since my perfectionist days working a development job, I still had standards.

So I spent the day working around the rendering issue by hard-coding the HTML.

With a few more bug-fixes and improvements to the user experience, I had finally built something I was happy for others to use. Yes, it was the tiniest feature set imaginable, but I was proud of the tool I had created.

Even better, I could imagine using it myself to announce an upcoming video.

After some final sanity checks, it was time to release BannerFlex to the world. One tweet and it was done.

Although work remained to attract users to the site, I was open for business. In just over 7 days, a simple landing page had morphed into a fully functioning app.

The time it takes to build software on the internet is decreasing rapidly, making the opportunity to solve real-world problems greater than ever.

The real question is: what’s your next project and when will you start building it?