Coming from JavaScript/TypeScript? Learn Go patterns that click — APIs, concurrency, and the stdlib way.
---
name: go-for-web-devs
description: Learn Go coming from JavaScript/TypeScript
author: mager
version: 1.0.0
---
# Go for Web Devs
You know JavaScript. Maybe TypeScript. You've built Express apps, Next.js sites, maybe some Deno. Now you want to learn Go. Here's the stuff that clicks when you stop thinking in JS and start thinking in Go.
## Why Go?
- **One binary.** No node_modules. No runtime. Build it, copy the file, run it anywhere.
- **Fast.** Not "fast for a scripting language" — actually fast.
- **Concurrency built in.** Goroutines and channels instead of callback hell.
- **Opinionated.** One way to format code. One way to handle errors. Less decisions = more building.
- **Stdlib is stacked.** HTTP server, JSON, crypto, testing — all built in. No Express needed.
## The Mental Model Shift
### JS → Go Translation Table
| JavaScript | Go | Note |
|-----------|-----|------|
| `const x = 5` | `x := 5` | Short declaration |
| `let x: number` | `var x int` | Explicit type |
| `function add(a, b)` | `func add(a, b int) int` | Return type declared |
| `async/await` | goroutines + channels | Different paradigm |
| `try/catch` | `if err != nil` | Errors are values |
| `null/undefined` | zero values | No null — every type has a default |
| `interface` | `interface` | Implicit (no "implements") |
| `npm install` | `go get` | Modules, not packages |
| `console.log` | `fmt.Println` | |
| `JSON.parse` | `json.Unmarshal` | Struct-based |
### Errors Are Values, Not Exceptions
This is the biggest mindset shift. In JS you throw and catch. In Go, every function that can fail returns an error.
```go
data, err := ioutil.ReadFile("config.json")
if err != nil {
return fmt.Errorf("reading config: %w", err)
}
```
It feels verbose at first. Then you realize: you handle EVERY error at the point it happens. No more "uncaught exception at line 847" in production.
### Structs Instead of Objects
Go doesn't have classes. It has structs + methods.
```go
type User struct {
Name string \`json:"name"\`
Email string \`json:"email"\`
Age int \`json:"age"\`
}
func (u User) IsAdult() bool {
return u.Age >= 18
}
```
The \`json:"name"\` tags are how Go knows to marshal/unmarshal JSON. This replaces your TypeScript interfaces AND your serialization layer.
## Building an API (The Go Way)
### Stdlib HTTP Server
You don't need Express. The standard library has everything.
```go
package main
import (
"encoding/json"
"net/http"
)
func main() {
http.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
http.ListenAndServe(":8080", nil)
}
```
That's it. No dependencies. No package.json. No node_modules.
### When to Add a Router
The stdlib mux is basic. For real APIs, use:
- **Chi** — lightweight, stdlib-compatible, my go-to
- **Gin** — popular, fast, more opinionated
- **Echo** — similar to Gin, good docs
Chi example:
```go
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Get("/api/users/{id}", getUser)
r.Post("/api/users", createUser)
http.ListenAndServe(":8080", r)
```
### JSON Handling
In JS: `JSON.parse(body)` and hope for the best.
In Go: define a struct, unmarshal into it, and the compiler catches mistakes.
```go
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
```
## Concurrency (The Superpower)
### Goroutines = Lightweight Threads
```go
go fetchData() // runs concurrently, that's it
```
This is like `Promise.all` but for everything, and goroutines cost almost nothing to spawn.
### Channels = Communication
```go
ch := make(chan string)
go func() { ch <- "hello" }()
msg := <-ch // blocks until message arrives
```
Think of channels as a typed, thread-safe queue. Goroutines send, other goroutines receive.
## Things That'll Trip You Up
1. **Unused variables are compile errors.** Not warnings. Errors. Use `_` to discard.
2. **Exported = capitalized.** `User` is public. `user` is private. No export keyword.
3. **No ternary operator.** Write the if/else. It's fine.
4. **Slices vs arrays.** Use slices (dynamic). Arrays (fixed size) are rare.
5. **Pointers exist** but aren't scary. `&x` = address of x. `*p` = value at pointer p.
6. **`go fmt`** formats your code. Don't fight it. There are no style debates in Go.
## Agent Behavior
- Translate JS/TS patterns to Go equivalents when the user asks "how do I do X?"
- Emphasize stdlib solutions before third-party packages
- Show error handling patterns — this is where JS devs struggle most
- When writing Go code, always handle errors (no `_` for errors unless intentional)
- Compare and contrast with JS/TS — "in JS you'd do X, in Go the equivalent is Y"
- Encourage running `go fmt` and `go vet` — the tooling is part of the language