I recently took some time to create a small web application that I ended up calling Svadilfari. This was a project I worked on alone on some weekends around my full-time job. This project is a good example of how I have developed my process of developing personal projects alone. The way I tackled this project was informed by my experiences and failures over the years. I want to go over how I came up with the idea for this project, planned it out and built it. The most important part of this is how and why I made the decisions I did.
The initial idea came for this project when I had some PDF files that I wanted to merge. These PDFs had been scanned in a haphazard way so that the exact size of each page varied. This annoyed me so I wanted to trim larger pages down. I also wanted to cut out some pages and wanted to turn an image into a PDF page to act as a cover photo. How did I achieve this? I used a combination of Google Docs and multiple websites with tools that each offered one specific way to manipulate PDF files. I went through this process thinking that my requirements can't be that unique and this is needlessly complicated. So I came up with the idea to make a single-page web application that lets users upload several images and/or documents in various formats, then weave them together and manipulate each page - or every page - exactly how you want.
There are a lot of questions to answer at this point. What features exactly should this tool have? What tools should I use to make it? Should users create an account or login with Google? Should the front-end be a React application? I know better than the start writing code or configuring infrastructure before answering these questions. Before I figure out what the implementation looks like, I have to figure out what the functionality looks like. But before I figure that out, I need to figure out what the user experience is like. But before I figure that out, I need to figure out what my overall goals and priorities are.
The priorities for this project are similar to any solo side project, but it is important to understand them.
- Finish the project. Only make something I have the free time and money to build and maintain.
- Make a clean, intuitive and accessible user interface.
- Make something with functionality that impresses people who potentially want to work with me or employ me.
This list is ordered, which is important. If I ever have to choose whether to implement something that improves the user experience, or keep the project scope feasible, I have to choose the more important thing: Having a finished product. What's the point of how hypothetically great my unfinished project would have been? If I have to choose between improving the experience of using basic functionality or adding more functionality, I am going to choose to make the existing experience better. If something still looks ugly or feels clunky to use, then adding more things a user can do will feel like a waste of time.
Before getting into the implementation details, I need to understand what this application will actually do. My initial idea was a bit vague and I need to make sure I don't get lost endlessly trying to add bells and whistles.
Is this application going to need users to be able to register accounts and login? If so, that introduces the need for a database, sending emails and a more complex web server. My goals indicate that I should avoid this because a feasible project and a user workflow that requires fewer steps both outweigh more features. A good design for this application is short on things to remove, not short on things to add.
The basic functionality I want is to be able to upload any combination of PDFs, documents or images. Then I want to specify any number of copies of each file, or page ranges for each file. I want to be able to order these, resize them and rotate them. Then I want to be able to output either a single PDF file or a ZIP containing a PDF for each page. This is the functionality I decided on. Anything else that demands a significant amount of my time is now off the table.
I need to figure out what the user interface is going to look like. To do this, I need to think through what the user is actually going to do from start to finish. This is where people talk about the concept of "user stories" were you write out what your application does in term of what the user is doing. This is a simple case where the entire application is a linear process the user goes through. User stories could look something like this:
- User uploads multiple documents in different file formats
- User selects multiple page ranges per document and orders them
- User specifies that they want every page cropped or pagged to a standard size
- User presses a button and sees the PDF they wanted in a new tab
This informs what UI components I need to build. The user can't do anything until they upload their files. This tells me there should be little on the screen aside from an upload button that stands out. Now the user needs to be able to specify all of the details of what they want their output to look like. The UI components need to display the uploaded files and the details of how the final product should look. This led me to designing a drag-and-drop feature to move uploaded files into a "workbench" grid. The user will want to preview this in some way to understand what they are getting before they finally create their finished product. Now I want some sort of panel with preview images and a big flashy BUILD button.
After figuring out the UI components and order of events, I put together a rough idea of how I want everything to be laid out on a webpage. My original idea pretty much looks like the final product. It came out working how I expected it to in my head. For the most part, I am happy with how it looks. Maybe there are ways to make it looking much better, but this is where my UI design skills are right now.
Another thing I need to figure out before I start building this is what to call it. This decision could impact the theme and style of the application. For instance, the popular tool GitKraken can use its pun-based name to make Kraken logos. There were not any obvious names or combinations of words that I liked. Something like FileBuilder would not make me as interested in making it or talking about it. That made me look for analogies. What else makes things quickly? Who is good at making things? I ended up choosing Svadilfari from Norse mythology. Its a niche reference and a silly analogy but its unique. It also feels somehow right in a way I can't explain. How does this name impact the product? Horse logo. Definitely horse logo.
I made horse logos in GIMP with stock footage of horses. This was an important use of my time. This was good for my morale while I spent weekends building this.
I also played around with colors until I landed on this color scheme. Somehow this feels right, but I can't explain how because color theory is a form of magic that science cannot explain.
The next step is to figure out how I am going to build this. What programming languages, libraries, tools, etc. I have to figure out both the front-end, the webpage the user interacts with, and the back-end, the server that handles much of the functionality.
For the front-end functionality, I ended up going with plain Javascript over using any framework at all. I could have used React - or any SPA framework - but realistically the amount of time I would have spent setting it up, troubleshooting it and writing more code would have put the goals of this project in serious risk. Delivering a finished product that I have the time to tweak would have been at too much risk. Frameworks are great for larger projects, especially when there is a dedicated team behind it. The benefits of SPA frameworks don't really impact this project either. The UI is hardly going to be reusing components or will not need any real-time updates.
For the back-end, I need a web server with a few API endpoints. I need to temporarily store uploaded files and track user sessions but I don't really need any kind of database. Node.js with Express has already become my go-to solution for rapidly building web servers. Unfortunately this does mean I have to deal with NPM and deal with Javascript dependencies on this side of the project. There is no particular reason in general to choose this over Django, Ruby on Rails or other alternatives. However, my goals with this project do not involve learning new frameworks. Sometimes I make something to learn new tools as I use them, but I already decided what this project is about. Since the differences between frameworks are minor, my established preference is the correct choice for my goals.
The web server has a bit of extra complexity in handling storing files and security. Every API endpoint needs to validate parameters and uploaded files need to have their sizes and types checked. I should be rejecting files that are too large. I should also be limiting the total file size for a user and for all users to limit storage space. I should also be deleting files after a day if not hours. Some of the endpoints may return messages questioning whether you are Loki messing with me if you try to call them with malicious-looking input. This is important because I get to giggle at my own nonsense while spending extra time just in case somebody decides my website looked at them the wrong way.
For testing, I need to set up a Docker environment that lets me access this web server at localhost:80. I am also going to want scripts to restart this test server, and to deploy to my production server in AWS.
Now I have my "stack"; the combination of tools that I will use to build this. The next step: Prepare for implementation.
I created a long indented bullet-point list of tasks, breaking up tasks into subtasks to give myself a long list of small action items. I no longer have this document since I tore it apart as I progressed. Doing this ensures that I don't get stuck trying to figure out where to start tackling something.
Making Svadilfari requires me to learn how to do a lot of things I have never done before. I came into this project with a limited understanding of how I would handle users uploading files. I also did not know what kinds of tools existed to handle manipulating various file formats. I queried Google and openAI for answers to technical questions probably more than a thousand times. I found pdf-lib for manipulating PDF files. I found pdf2pic to convert PDF pages to images. I found sharp for image manipulation. For documents, I ended up deciding to install LibreOffice - A 1.5 Gigabyte application - on the server just to convert various document files to PDFs. This is reasonable.
Getting all of these pieces working together mostly comes down to searching skills. If Google knows something, then the information is already in my post-frontal cortex.
For testing in docker, I made myself a bash script that just executes this command: docker exec portfolio pm2 restart svadilfari. Making a script is necessary because I want to do everything in my power to avoid ever having to type multiple words.
During testing, I made several small changes to my design. Things like the workspace having large text indicating that you can drag-and-drop uploaded files, and the hover color effects. The little details, made with just a bit of CSS, make a big difference in how satisfying it feels to use. I only started learning about UI design after university and I have struggled to understand it. I do enjoy dabbling in this side of software development. I am intrigued by how abstract and subjective it is compared to back-end development.
I did also add some easter eggs that are on-theme with the name I chose. The file that is downloaded defaults to being named "walls-of-asgard" and some error messages mention Loki. These kinds of things add uniqueness to the application without inconveniencing the user. Solo projects can be a good opportunity to experiment and create something that feels distinctly like you made it.
Once things seem to be working in Docker, I need to deploy to "production" - the server where my website is hosted. The first time I deploy, I always do it manually using MobaXTerm. The first deployment is where I learn the details of how the deployment works. After that, I make a script to automate subsequent deployments. The deployment script for Svadilfari looks something like this:
#!/bin/bash
# set variables
front_end_only=false
for arg; do
if [[ $arg == "-f" ]]; then
front_end_only=true
echo "Front-end only mode"
fi
done
# create a minified JS file
uglifyjs client/js/svadilfari.js -m -c drop_console=true -o client/js/svadilfari-min.js
# make the HTML file include minified JS in prod
sed -i -e 's/svadilfari\.js/svadilfari-min.js/g' $(pwd)/ejs/svadilfari.ejs
scp -i "$keyfile" $(pwd)/*.* $adam@$adamsworld:$deploymentroot/.
scp -i "$keyfile" $(pwd)/ejs/* $adam@$adamsworld:$deploymentroot/ejs/.
if ! $front_end_only ; then
scp -i "$keyfile" $(pwd)/server/*.ts $adam@$adamsworld:$deploymentroot/server/.
scp -i "$keyfile" $(pwd)/server/util/* $adam@$adamsworld:$deploymentroot/server/util/.
scp -i "$keyfile" $(pwd)/server/types/* $adam@$adamsworld:$deploymentroot/server/types/.
scp -i "$keyfile" $(pwd)/server/route/* $adam@$adamsworld:$deploymentroot/server/route/.
fi
scp -i "$keyfile" $(pwd)/client/img/* $adam@$adamsworld:$deploymentroot/client/img/.
# deploying css, not less
scp -i "$keyfile" $(pwd)/client/css/*.css $adam@$adamsworld:$deploymentroot/client/css/.
# deploying minified JS, not the original
scp -i "$keyfile" $(pwd)/client/js/*-min.js $adam@$adamsworld:$deploymentroot/client/js/.
if ! $front_end_only ; then
ssh -i "$keyfile" $adam@$adamsworld "cd /app/svadadamsavard/; npm install;"
ssh -i "$keyfile" $adam@$adamsworld pm2 restart svadilfari
fi
# make sure the HTML file still includes non-minified JS in test
sed -i -e 's/svadilfari-min\.js/svadilfari.js/g' $(pwd)/ejs/svadilfari.ejs
echo "Completed at ".`date`
If made a front-end only option because if I only modified those files, there is no need to update or restart the web server. During my first deployment, I set up a subdomain of my website to direct traffic to this server. This is not something I need to automate since I will not do it again.
I came back to this project later to make some minor enhancements. I think the last things I added were the trash can and "What is this?" about section. Those were not in my original design but felt like they were missing. The about page is also a great place to expand on the important lore that was previously only available via hacking or finding the 404 page.
If I ever return to this project to make it into something larger, I will at least look into lightweight SPA frameworks for the front-end to scale it up sustainably. I am more likely going to be focusing on other projects instead.