What building my own projects has taught me

Some of the most useful things I’ve built have started with a fairly small question. Could I make this interaction easier to reuse? How should these records fit together? What happens if I move an item, reload the page and expect it to stay where I left it? A project gives me somewhere to work those questions through.

Alongside my agency work, I’ve spent time building JavaScript tools, a Laravel file manager, a personal dashboard and experiments with Kanban boards. They cover different parts of web development, but the lessons overlap. A feature needs a clear purpose, its data needs somewhere sensible to live, and the interface needs to make the result understandable.

Looking back over those builds this year, I can see how much they have shaped the way I approach client work. I still enjoy experimenting. I’m getting better at deciding what I want an experiment to teach me, and recognising the work that remains after the first version appears to function.

Small components expose the fundamentals

A small component is a good place to find gaps in my understanding. An accordion asks fairly direct questions: what opens it, where does its state live, and how does someone use it without a mouse? A gallery adds another set of decisions about loading, selection and movement. There is enough to think about without needing an entire application around it.

My CSSColumnPro work takes a similarly focused approach to layout. I wanted to explore a smaller column system with the familiar container, row and column structure. The project uses Sass, flexbox and CSS variables for column widths and gutters, with a visual grid overlay to help inspect the layout.

Building that made the relationship between the design and the implementation quite tangible. A gutter has to behave consistently. A column needs to make sense at different widths. A layout has to survive a longer heading or a piece of content that does not match the example. Those details are easy to overlook when the demonstration contains two short sentences.

I still use established tools where they fit. Making a smaller version of something helps me understand the choices those tools make, and decide how much of them a particular website needs. The useful outcome can be a better understanding of CSS just as much as a reusable stylesheet.

State is part of the design

Working on Intly, my own JavaScript framework, gave me more room to explore shared state, toggles, page loading and reusable functions. I wanted the code behind sliders, galleries and popups to have a structure another developer could follow. Learning about functional programming encouraged me to break behaviour into smaller pieces with clearer inputs.

That does not mean every part of the framework is purely functional. It changes the page and maintains state. The useful lesson was to make those changes easier to find and understand. If a toggle changes a value, a class and an accessible state, I want a clear relationship between those things.

Dynamic page loading brings the same issue into sharper focus. Replacing a section of HTML is only the beginning. The new content needs the right behaviour, the page title needs to make sense, and navigation needs to remain understandable. I need to know which setup belongs to the whole website and which belongs to the content that has just arrived.

Caching taught me to ask another question: when should a saved value stop being trusted? Keeping a response in local storage can avoid repeated work, but old data can also produce a very convincing wrong answer. Expiry, invalidation and failure handling deserve as much thought as the first successful cache hit. A quick demonstration does not settle those decisions.

The data model keeps turning up in the interface

The Laravel file manager was particularly interesting because the interface had a familiar shape while the structure underneath needed careful thought. People understand folders and files. Making that familiar experience work involves parent relationships, paths, uploads and deciding who should have access.

In that build, a shared object structure connects folders and files into a tree. It gives the application a way to find children and ancestors, and lets the interface show where someone is in the folder structure. Laravel’s models and relationships helped me organise that work into pieces I could reason about.

It also made the connection between permissions and design much clearer. A download button is one small part of the journey. The server still needs to decide whether the person requesting the file may receive it. The same thinking applies when creating a folder, uploading into it or removing something that has other items beneath it.

I would give those checks more attention from the beginning of another build. It is easy to get absorbed in the browsing and upload experience because that is the part I can see. The rules behind it need their own deliberate review. An interface that looks complete can still have important decisions left unresolved.

A full-stack project makes the connections real

Building my personal dashboard with the MERN stack helped me understand the complete journey of a piece of information. React renders the interface, Express and Node handle requests, and MongoDB stores the documents. Working with all of them in one project gave each part a more practical meaning.

The dashboard includes account cards, bill information and bookmarks. That gave me actual records to create, edit and display, instead of learning each tool through an unrelated example. I could follow an action from the screen to a request, through a query and back to the interface.

MongoDB’s flexibility was enjoyable to work with, but it also made me think harder about the shape of the data. A document still needs consistent fields and a clear relationship to the person who owns it. If I leave too much undefined, that uncertainty turns up later in validation, calculations and the interface.

Talking about this work with other developers helped a great deal. Explaining how I had organised a request or stored a record forced me to be more precise. Another person’s question could uncover an assumption I had carried through several parts of the application. I learn more when I can discuss something I have actually tried.

An interaction has to survive the save

This year’s Kanban experiment has been another useful way to explore those connections. It brings together a React interface, an Express backend and MongoDB models for boards, columns and tasks. Moving columns around gives the interface an immediate sense of progress.

The implementation sends the new column order to the backend before updating the displayed list. That makes saving part of the interaction. It raises useful questions: what should someone see while a request is running, and how should the page explain a failed save? Moving an item successfully on screen is only convincing if the stored result agrees.

Those questions apply well beyond a board. Editing a bookmark, changing a product or uploading a file all create a period when someone needs to know what the system is doing. I want clear feedback and a sensible way to recover, rather than an interface that leaves them guessing whether an action worked.

My design background helps here. Spacing, labels and familiar controls matter, but so do loading, empty and error states. They are part of the same experience. I find it useful to work through them while the feature is still small, before several screens start relying on the same unclear behaviour.

Proof is more useful than confidence alone

The conveyor-belt interview challenge is still a good reminder of how satisfying a contained problem can be. I had to implement specific rules in vanilla PHP and show the result. Being offered the job after handing it in gave me a real confidence boost, and confirmed how much I enjoy working through that kind of logic.

It also gives me something useful to reflect on now. A visible demonstration can explain a solution, but repeatable checks make it easier to know whether a later change has broken it. Known inputs, expected outputs and awkward cases are worth defining early. A successful example leaves plenty of room for another sequence to behave differently.

I try to carry that into experiments without turning every small project into a huge exercise. For a data feature, I want to check that the change survives a reload. For a component, I want to check its other states and keyboard behaviour. For a permission, I want to check the request itself. The proof should match the thing I’m trying to establish.

Learning something is a useful result

There is a temptation to keep adding features because each one suggests another. A dashboard can quickly become a much larger system. A helper can become a framework. I enjoy that possibility, but I’m learning to give an experiment a smaller first question and leave room to answer it properly.

Some projects will become useful tools; others will remain places where I tried an approach. Both can improve my work. The important part is being able to explain what I learned, what I would keep and what I would change before relying on it for someone else.

By this point in 2024, I feel more confident moving between the interface, the application logic and the data underneath. I still have plenty to learn. Building things gives me a practical way to do that, and the lessons keep finding their way back into the websites and applications I work on every day.

More about me