How to setup fullstack web app with golang and vite?

Build web app is getting easier these days, new tools are coming out everyday with the goals to help developers to get the job done. Every new tools offer new architecture new technology and a lot more new thing, sometime that new things not helping us to get the job done there are a lot of experimental things that hard to learn.

In this article I wanted to share my choice for fullstack web framework, or actually it's not a framework. It just golang for backend and vite for frontend.

Why choosing golang?

First of, golang is very cheap. My side project Readclip which is I built with Golang and Vite only take about 60MB image size in docker. If I compare this image size to docker for laravel, ruby or remix my stack produce much smaller size in docker size.

Well, you might think docker size is not much of concern that is okay. But for me being able to produce smaller docker image size will help me optimize my resource usage, and packaging golang app into docker is very easy if we compare it to dockerizing framework like laravel, remix or nextjs.

What about scale?

For most of the cases golang out perform popular languages php, nodejs, ruby and more. Golang is simple language even though it's not strongly typed like rust and typescript the type system of golang is enough to write performant code.

What about ecosystem?

Golang is used in a lot of place, easy to deploy easy to install dependencies and it supported by large company like goolge, uber and more.

But yeah there are a lot of debate when choosing programming language, let's just stop here and focus on building software shall we?

Why vite?

I'll just give short answer to this, vite it just works, it easy to to work with any modern frontend framework that works in client, I think that is the responsibility of frontend framework it shouldn't care what happend in the backend. Let golang deal with the backend stuff.

In this article we will use React, because that is the only frontend framework I am familiar to. But feel free to choose your favourite frontend frame works.

Setup project

Let's create new project folder.

mkdir golang-fullstack-vite
go mod init

In this project we will use GoFiber as our web server that we will create with golang.

Create file main.go:

package main

import (


func main() {
    app := fiber.New()

    app.Get("/hello", func (c *fiber.Ctx) error {
        return c.SendString("Hello, World!")


Install dependencies:

go mod tidy

Create new folder for vite project let's just name the folder ui.

npm create vite@latest

Need to install the following packages:
Ok to proceed? (y) y
✔ Project name: … ui
✔ Select a framework: › React
✔ Select a variant: › TypeScript

Create file embed.go, we will use this to include vite build file into the binary file when we compile the go program so that we don't need to include folder when deploying our app.

package ui

import "embed"

//go:embed dist/*
var Index embed.FS

Now we can serve the frontend into our go fiber web server.

import (


func main() {
    app := fiber.New()

	index, err := fs.Sub(ui.Index, "dist")
	if err != nil {

	app.Use("/", filesystem.New(filesystem.Config{
		Root:   http.FS(index),
		Index:  "index.html",
		Browse: false,

    app.Get("/hello", func (c *fiber.Ctx) error {
        return c.SendString("Hello, World!")



With this setup you might got a problem, so here's the problem I found so far and how to fix it.

Serving custom static url

By default this setup will enable singgle page application from / end point. But if you wanted to serve custom url you ca do this.

serveUI := func(ctx *fiber.Ctx) error {
		return filesystem.SendFile(ctx, http.FS(index), "index.html")

	uiPaths := []string{

	for _, path := range uiPaths {
		app.Get(path, serveUI)

Deal with cors

In local development you might need to enable cors to access the api from frontend.

import (

    AllowOrigins: "http://localhost:3000,",
    AllowHeaders: "Origin, Content-Type, Accept",

Don't crash app when fatal error occur

import (


Loading env variable

I use godotenv to parse env to struct, that env value can be set to system environment variable or .env file.

package config

import (

type Config struct {
	Port              string `env:"PORT" envDefault:"8080"`
	GoogleCredentials string `env:"GOOGLE_APPLICATION_CREDENTIALS" envDefault:""`
	DatabaseUrl       string `env:"DB_CONNECTION_STRING" envDefault:""`

func Load() *Config {
	cfg := Config{}
	return &cfg

How to handle Authentication?

So far I use firebase for authentication it easy to setup for backend and frontend. Use gofiber-firebaseauth to validate authorization from firebase.

import (
    gofiberfirebaseauth ""

    FirebaseApp: firebaseApp,
    IgnoreUrls: []string{
    ErrorHandler: firebase.ErrorHandler,

If you still have a problem feel free to send me a question I will try to help you to solved it.


Building the docker is easy. So here's the docker file you will need.

FROM golang:1.20.1-alpine as base
RUN apk add curl bash nodejs npm make
RUN npm install --global pnpm

WORKDIR /go/src/app
COPY go.* .
RUN go mod download

COPY . .
RUN make build TARGET_DIR=/go/bin/app

FROM alpine:3.17.2
COPY --from=base /go/bin/app /app

CMD ["/app"]

This docker file is calling Makefile to run the build, this is usefull because we can use Makefile to run our development server as well. Here's the Makefile config.

.PHONY: build
TARGET_DIR ?= build/app
	(set -e; cd ui && pnpm i && pnpm run build)
	go generate ./...
	CGO_ENABLED=0 go build -o ${TARGET_DIR} -buildvcs=false

	cd ui && npm run dev

	@source .env && npx concurrently "cd ui && npm run dev" "go run main.go"

	docker compose up -d

	docker compose up --force-recreate --build app

	flyctl deploy ## You can use any deployment scripts here