DEV Community

A0mineTV
A0mineTV

Posted on

Building SearchForge: a lightweight search interface with Go and Vue

I like building products that are small, focused, and easy to reason about.

That is exactly why I started SearchForge: a local search application with a Go backend and a Vue 3 + TypeScript frontend, designed to feel like a modern SERP while staying simple enough to evolve quickly.

The project is available on GitHub here: VincentCapek/SearchForge.

The goal was not to recreate a full search engine from day one.

The goal was to build a clean foundation:

  • a backend that can fetch and normalize results,
  • a frontend that can render them in a familiar way,
  • and an architecture flexible enough to support multiple search providers later.

Why I built it

I wanted a project that sat at the intersection of backend structure, frontend UX, and search-oriented interfaces.

Search is a great playground for that because even a “simple” search page forces you to think about several layers at once:

  • how results are fetched,
  • how they are normalized,
  • how errors are handled,
  • how loading states feel,
  • and how the UI stays readable when the data changes.

Instead of starting with a huge stack, I deliberately chose a lightweight setup:

  • Go for the backend,
  • Vue 3 + TypeScript for the frontend,
  • Vite for fast iteration,
  • and UnoCSS for a design system that stays close to the code.

The architecture

From the beginning, I wanted the codebase to be split by responsibility rather than mixed together in a single app layer.

The project is organized around a few simple parts:

  • search/ contains the search fetching and parsing logic
  • server/ contains the HTTP server and route registration
  • server/api/ contains the API handlers and JSON error helpers
  • assets/ contains the frontend application
  • utils/ is where shared helpers live

That separation matters more than it seems.

It means I can improve parsing logic without touching the UI, and I can redesign the frontend without rewriting the backend contract.

Why Go for the backend

For this kind of project, Go felt like a natural fit.

I wanted a backend that was:

  • fast to run locally,
  • easy to read,
  • simple to deploy later,
  • and not dependent on a heavy framework just to expose a few endpoints.

So I used the standard library HTTP server and built a small router with dedicated handlers.

That gave me a backend that stays explicit:

  • a health endpoint,
  • provider-specific search endpoints,
  • and a small JSON layer for success and error responses.

One detail I particularly like is the generic handler factory.

Instead of repeating the same validation and response logic for every provider, I used a generic makeSearchHandler function that wraps a search function and turns it into an HTTP handler.

That keeps the API layer small while making it easy to plug in new providers.

Fetching and parsing search results

The core of the project lives in the search layer.

Right now, the backend already includes logic for multiple providers, including DuckDuckGo and Brave. The idea is straightforward:

  1. build the search URL,
  2. fetch the HTML document,
  3. parse the relevant nodes,
  4. normalize the output,
  5. return a consistent JSON response to the frontend.

To do that, I used goquery, which gives me a jQuery-like API for HTML parsing in Go.

For DuckDuckGo, I parse result blocks, ignore ads, extract the title, URL, domain, and snippet, then normalize the URL before returning it.

For Brave, I follow a similar flow, but with selectors adapted to Brave’s markup and a few extra fields such as site metadata.

This is one of the most interesting parts of the project because scraping search results is never just “grab some HTML and done.”

You have to think about:

  • noisy markup,
  • invalid links,
  • duplicate results,
  • result normalization,
  • and how brittle selectors can become over time.

Adding a small relevance filter

I also added a lightweight blocklist layer.

This lets me exclude domains that are not useful for the kind of result quality I want in the interface. It is a tiny feature, but it changes the feel of the output immediately.

I like this kind of pragmatic layer because it shows that ranking and filtering are not always giant machine learning problems.

Sometimes, a simple handcrafted rule already makes the product feel better.

Designing the frontend

On the frontend side, I wanted the UI to stay clean and component-driven.

The app is built with Vue 3, TypeScript, and Vite, and split into small pieces:

  • a search bar,
  • a result list,
  • individual result cards,
  • and a composable dedicated to the search flow.

That composable is important.

Instead of scattering state across the app, I grouped the core search state into a single place:

  • the query,
  • loading,
  • error,
  • results,
  • and whether the user has already searched.

That makes the UI easy to reason about because each visual state maps to a clear application state:

  • initial state,
  • loading state,
  • empty state,
  • error state,
  • and result state.

This is the kind of pattern I like in frontend projects: not complicated, just predictable.

Why I used UnoCSS

I chose UnoCSS because I wanted utility-first styling without turning the code into an unreadable wall of classes.

The project defines shortcuts and theme tokens for:

  • layout,
  • search fields,
  • buttons,
  • result cards,
  • feedback panels,
  • and typography.

That means I can keep a consistent visual language while still moving fast.

I also like that the design system already reflects the kind of interface I wanted: soft borders, muted text, clean spacing, and a search-first layout that feels familiar without being a copy.

Keeping frontend and backend decoupled in development

One practical choice that helped a lot was using Vite’s proxy.

The frontend runs on its own dev server, while API and health requests are forwarded to the Go backend. That keeps the local workflow smooth:

  • run Go on port 8080,
  • run Vite on port 5173,
  • and let the frontend talk to the API without CORS headaches.

It is a small detail, but it makes iteration much faster.

What I like about this project

What I like most about SearchForge is that it already has the right shape.

It is not trying to do everything.

It is trying to do the important things clearly:

  • separate concerns,
  • normalize data at the backend layer,
  • keep the frontend simple,
  • and leave enough room for future expansion.

There are even traces of its earlier naming and evolution in the codebase, which I honestly like. It reminds me that good projects are rarely born “finished” — they become clearer as you build them.

What I would improve next

This project is already a strong base, but there are a lot of directions I want to explore next.

A few obvious ones are:

  • adding more providers,
  • introducing filters and engine selection,
  • improving normalization across providers,
  • storing search history,
  • serving the frontend directly from Go in production,
  • and refining the SERP UI further.

I also want to keep improving the contract between backend and frontend so each provider can plug into the same interface cleanly.

That is where this kind of project becomes really fun: once the foundation is stable, every new feature has a clear place to live.

Final thoughts

SearchForge is the kind of project I enjoy building the most: small enough to move fast, but structured enough to grow into something more serious.

It gave me a chance to work on:

  • backend API design,
  • HTML parsing in Go,
  • provider normalization,
  • frontend state management,
  • and design system thinking inside a Vue app.

Most importantly, it helped me build a product in layers instead of building everything at once.

That is usually how I like to work:
start with a clear backbone, make each part understandable, and leave room for the project to become more ambitious over time.

If you are interested in lightweight full-stack projects, search interfaces, or pragmatic architecture, this kind of setup is a lot of fun to build.

You can explore the codebase here: github.com/VincentCapek/SearchForge.

Top comments (0)