A deep dive into YSvelGoK: Combining SvelteKit, Go (Gin), and MongoDB into a dockerized powerhouse.
Why I Paired Svelte with Go
We often find ourselves choosing between Developer Experience (DX) and Raw Performance.
- Node.js/Next.js: Amazing DX, huge ecosystem, but can get heavy.
- Go: Incredible performance, tiny implementation, but authenticating and structuring a full-stack app from scratch takes time.
I wanted the best of both worlds. So I built YSvelGoK (Yaxel's Svelte + Go Kit). Here's how it works under the hood.
The Stack
- Frontend: SvelteKit (Svelte 5)
- Backend: Go with Gin
- Database: MongoDB
- Runtime: Bun (for frontend builds) & Docker
🔐 The "Soft Session" Authentication Pattern
Authentication is usually the biggest pain point in Go. I didn't want to rely on a third-party service like Auth0 for a boilerplate, but I also wanted more security than a standard stateless JWT.
I implemented a hybrid approach I call Soft Sessions.
How it works:
- Login: User logs in, backend verifies Argon2 hash.
- Session Record: A simple document is created in MongoDB (
_id,user_id,created_at). - Token Issue: A JWT is signed containing the Session ID (not just the User ID).
The Secret Sauce: Middlewares
In my Go middleware, I don't just check the signature. I also verify the session is alive in MongoDB.
// api/Routes/middleware.go
func RequireSessionValidate() gin.HandlerFunc {
return func(c *gin.Context) {
claims := GetCheckedClaims(c)
// 1. Signature Check
if claims == nil || claims.Expired() {
c.AbortWithStatusJSON(401, gin.H{"message": "Unauthorized"})
return
}
// 2. Database "Liveness" Check
// If the session was deleted from DB (logout), this fails
// even if the JWT is still mathematically valid.
user, session, err := Db.ValidateJWTSessionFromClaims(claims, false)
if err != nil {
c.AbortWithStatusJSON(401, gin.H{"message": "Session Revoked"})
return
}
c.Set("user", user)
c.Set("session", session)
c.Next()
}
}
This gives us instant revocation (like sessions) with the structured claims of JWTs. MongoDB handles the cleanup automatically via a TTL Index on the sessions collection.
🐳 Dockerizing the Chaos
Orchestrating a frontend, backend, and database manually is annoying. I used Docker Compose to bundle it all.
The coolest part? Using depends_on with healthcheck to ensure the API never crashes because the Database wasn't ready yet.
database:
image: mongo:latest
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
api:
build: ./api
depends_on:
database:
condition: service_healthy
🏎️ SvelteKit on the Frontend
The frontend uses SvelteKit, but configured to work seamlessly with an external Go backend.
I use a Server Hook (hooks.server.js) to parse the JWT from cookies before the page even renders. This allows the SSR (Server Side Rendering) to know if a user is logged in immediately.
// frontend/src/hooks.server.js
export const handle = async ({ event, resolve }) => {
const token = event.cookies.get("token");
if (token) {
// Optimistically verify signature for UI state
// Actual data fetching will still be verified by Go
const { payload } = await jwtVerify(token, SECRET);
event.locals.user = payload;
}
return await resolve(event);
};
Wrapping Up
This architecture has become my go-to for starting new projects. It's type-safe, compiles fast, and the frontend feels incredible.
The code is open source. Feel free to clone it, break it, and fix it!
Top comments (0)