When I got to the Recurse Center a week ago I decided to make a simple full-stack app to learn Go, get experience with Vue, and shore up my professional full-stack knowledge with some experienced oversight and code review. The app I landed on was a simple ticketing app that mocks events, allowing users to save events they’re interested in and “buy” tickets for events. Here’s how it went.

The source code can be found on my github and, for a limited time, the demo can be found here.

Frontend

I chose Vue because of its wide adoption, and easy transition from React. I was not disappointed with my choice and enjoyed building UI with Vue’s intuitive component-based design. I’ve done very little frontend work and particularly enjoyed the ability to create “scoped” styles that allow haphazard css without interfering with other components.

1
2
3
4
5
<style scoped>
  button {  // we target a few global tags
    ...
  }
</style>

I also like the way that Vue handles template rendering with its v- attributes that supercharge templating with Vue’s magic.

1
2
3
<TicketsComponent class="item" v-for="(t, i) in ticketss" 
  :key="i" 
  :tickets="t"/> 

Vue comes with Vite out-of-the-box, which I learned can be setup to proxy requests to a backend. This dodges all CORS concerns and enables easy deployments with no need for sketchy CORS config changes.

Backend

With the frontend out of the way we can get to the server. I chose to use the echo server to handle routing, and middleware. Echo was very easy to use, but if I were to restart this project I would use the standard library instead, which is one of Go’s strongest features.

User Authentication

I’ve never done user auth before, but after a little bit of research I learned that its a large topic with lots of things to think about. I narrowed my scope to ignore problems like users creating multiple accounts or SSO and instead focused on JWTs.

JWTs, or JSON Web Tokens, are a method of user authentication where session state is stored on the client side via a signed-token. This differs from server Session Authentication where a database holds user sessions and gives users opaque session identifiers. JWTs allow the server to be truly stateless because no database lookup is required to verify user requests. It works like this:

  1. A user logs in / creates an account.
  2. The server creates a token. Claims are attached to the token to store information about the user and token expiratin date. The server then uses a key to sign the token.
  3. The token is sent with the response as a cookie.
  4. The user’s browser stores the cookie in cookie storage and sends it with all future requests.
  5. The server gets the cookie and verifies the JWT’s signature by recomputing its hash and checking that it matches the signed hash.

In practice this is easy to implement by using Go’s extensive set of publicly available packages. On account creation a username and password are sent via a secure connection to an endpoint where the password is hashed before storing it in the users table. On login the provided credentials are compared against the users table and if a match is found a JWT is returned. A successful response sets a cookie in the browser containing the JWT.

This application doesn’t store critical user data so security is considered for learning purposes. In a more serious application it would be necessary to put more thought into credential storage, and security verification.

Getting Go-y

Go doesn’t provide classes but has powerful structs and interfaces that allow inheritance-like patterns. I wrote a controller interface that defines the operations my endpoints will need. Then I implemented this interface on an Sqlite struct. The controller interface can be used as a type guaranteeing that should I need to switch to a more serious database the consumers of the interface could be left unchanged.

Go also has a feature called struct tags that lets struct attributes be tagged to be read later by different packages. This allows some very intuitive patterns for model serialization or other reflection-based functionality.

1
2
3
4
5
6
7
8
type BaseEvent struct {
	Id          int       `json:"id"`
	Kind        string    `json:"kind"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
	Venue       string    `json:"venue"`
	Date        time.Time `json:"date"`
}

Go’s built-in testing framework doesn’t provide assert statements which feels strange coming from Python, but its test CLI is easy to use and requires no configuration. I was also pleasantly surprised when the Go debugger worked with no config. I don’t think a debugger has ever worked without any config for me, which is a testament to Go’s design.

Deployment

Vue’s documentation on containerization was simple and correct providing me with no difficulties in containerizing my frontend and serving it with Nginx.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
FROM node:latest as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY ./ .
RUN npm run build

FROM nginx as production-stage
RUN mkdir /service
COPY --from=build-stage /app/dist /service
COPY nginx.conf /etc/nginx/nginx.conf

Go was nearly as easy to containerize with good options for base images and a descriptive error message telling me how to make Sqlite work. After containerizing both I directed Nginx to route "/api" requests to my Go container.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
FROM golang:1.22.5  
WORKDIR /service
COPY  . /service/
RUN apt-get update -y && apt-get upgrade -y
RUN apt-get install -y sqlite3 bash

RUN chmod +x scripts/make_db.sh && ./scripts/make_db.sh
RUN go env -w CGO_ENABLED=1
RUN go build .
EXPOSE 1323
CMD ["./backend"]

After a domain name purchase, git clone, and some configuration of Cloudflare tunnels my site is live.

Conclusions

Go is a promising language which I intend to continue exploring. Vue is now my go-to frontend framework. I chose good tools for this project and intentionally implemented more of it than I would in a real setting. If I were tasked with building ticketmaster in a professional environment I would employ a backend-as-a-service like Supabase and completely delegate security / auth to a 3rd party vendor.