Published 1/10/2025 · 7 min read
Tags: go , golang , javascript , backend , tutorial
Go for JavaScript Developers: The Very Basics
If you’re a JavaScript developer curious about Go, you’re in the right place. I’ve been writing JS for years and recently started exploring Go for backend work — specifically for building services that sync data between APIs and databases.
This tutorial assumes you know JavaScript but have never written a line of Go. We’ll start from absolute zero.
Why Go?
Short answer: Go is simple, fast, and compiles to a single binary you can deploy anywhere.
Long answer: Coming from JavaScript, you’re used to npm, node_modules, runtime versions, and deployment headaches. Go compiles your entire application into one executable file. Copy it to a server, run it. That’s deployment.
Go is also genuinely simple. The language spec is small — you can learn the whole thing in a few weeks. There’s usually one obvious way to do something, which means less decision fatigue.
Installing Go
On a Mac with Homebrew:
brew install go
Verify it worked:
go version
You should see something like go version go1.22.0 darwin/arm64.
That’s it. No version managers, no separate package managers. Go is refreshingly self-contained.
Your First Go Program
Create a new folder and file:
mkdir hello-go
cd hello-go
touch main.go
Open main.go and add:
package main
import "fmt"
func main() {
fmt.Println("Hello from Go")
}
Let’s break this down:
package main— Every Go file belongs to a package. Themainpackage is special: it’s where your program startsimport "fmt"— We’re importing thefmtpackage (short for “format”) which handles printing to the consolefunc main()— The entry point of your program. When you run the app, this function executesfmt.Println()— Prints a line to the console. Think of it likeconsole.log()
Run it:
go run main.go
You should see Hello from Go printed.
Variables: Where Things Start to Feel Different
In JavaScript, you might write:
let name = "Gareth";
const age = 30;
In Go:
package main
import "fmt"
func main() {
var name string = "Gareth"
age := 30
fmt.Println(name, age)
}
Two ways to declare variables here:
var name string = "Gareth"— Explicit declaration with type. We’re saying “create a variable callednamethat holds astring”age := 30— Short declaration. Go figures out the type from the value. This only works inside functions
The := syntax is what you’ll use most often. It’s Go’s equivalent of const in spirit — though technically Go variables are mutable.
Key difference from JS: Go is statically typed. Once a variable has a type, it can’t hold a different type:
age := 30
age = "thirty" // This won't compile
JavaScript would let this slide. Go won’t.
Functions
JavaScript:
function add(a, b) {
return a + b;
}
Go:
func add(a int, b int) int {
return a + b
}
func add— Declaring a function calledadd(a int, b int)— Two parameters, both are integers. Types come after the nameint(after the parentheses) — The return type. This function returns an integer
You can shorten repeated types:
func add(a, b int) int {
return a + b
}
When a and b are both int, you only need to write the type once.
Multiple Return Values
This is where Go gets interesting. Functions can return multiple values:
func divide(a, b int) (int, error) {
if b == 0 {
return 0, fmt.Errorf("cannot divide by zero")
}
return a / b, nil
}
(int, error)— This function returns two things: an integer and an errorfmt.Errorf()— Creates an error with a messagenil— Go’s version ofnull. When there’s no error, we returnnil
Calling it:
result, err := divide(10, 2)
if err != nil {
fmt.Println("Something went wrong:", err)
return
}
fmt.Println(result)
This pattern — returning a value and an error, then checking the error — is everywhere in Go. There’s no try/catch. You check errors explicitly after every operation that might fail.
It feels tedious at first. Give it a week. You’ll start to appreciate knowing exactly where things can go wrong.
Structs: Go’s Version of Objects
JavaScript objects are flexible:
const person = {
name: "Gareth",
age: 30,
};
Go uses structs, which are more rigid:
type Person struct {
Name string
Age int
}
func main() {
person := Person{
Name: "Gareth",
Age: 30,
}
fmt.Println(person.Name)
}
type Person struct— We’re defining a new type calledPersonName string— A field calledNamethat holds a string. Note the capital letter — this matters (we’ll get to why)Person{Name: "Gareth", Age: 30}— Creating an instance of the struct
Why capital letters? In Go, capitalisation controls visibility:
Name(capital) — Exported, accessible from other packages. Likeexportin JSname(lowercase) — Unexported, private to this package
This applies to functions, variables, and struct fields.
Slices: Go’s Arrays
JavaScript arrays:
const numbers = [1, 2, 3];
numbers.push(4);
Go slices:
numbers := []int{1, 2, 3}
numbers = append(numbers, 4)
[]int— A slice of integers. The empty brackets mean it’s a slice (dynamic length), not an array (fixed length)append()— Adds an element. Unlike JS, this returns a new slice rather than modifying in place
Looping through a slice:
for i, num := range numbers {
fmt.Println(i, num)
}
range— Iterates over the slicei, num— Each iteration gives you the index and the value
If you don’t need the index:
for _, num := range numbers {
fmt.Println(num)
}
The underscore _ tells Go “I’m ignoring this value”. Go won’t compile if you declare a variable and don’t use it — the underscore is the escape hatch.
Maps: Key-Value Pairs
JavaScript:
const scores = {
alice: 100,
bob: 85,
};
Go:
scores := map[string]int{
"alice": 100,
"bob": 85,
}
fmt.Println(scores["alice"])
map[string]int— A map with string keys and integer valuesscores["alice"]— Accessing a value by key
Checking if a key exists:
score, exists := scores["charlie"]
if !exists {
fmt.Println("Charlie hasn't played yet")
}
When you access a map, you can get two values back: the value and a boolean indicating whether the key existed.
Putting It Together
Here’s a small program that combines everything:
package main
import "fmt"
type Order struct {
ID int
Amount float64
Status string
}
func main() {
orders := []Order{
{ID: 1, Amount: 99.99, Status: "shipped"},
{ID: 2, Amount: 149.50, Status: "pending"},
{ID: 3, Amount: 29.99, Status: "shipped"},
}
total := calculateShippedTotal(orders)
fmt.Printf("Total shipped: £%.2f\n", total)
}
func calculateShippedTotal(orders []Order) float64 {
var total float64
for _, order := range orders {
if order.Status == "shipped" {
total += order.Amount
}
}
return total
}
- We define an
Orderstruct with three fields - Create a slice of orders
- Pass them to a function that filters and sums
fmt.Printflets us format output —%.2fmeans “float with 2 decimal places”
Run it:
go run main.go
Output: Total shipped: £129.98
What’s Next
This covers the absolute basics — enough to read Go code and understand what’s happening. Next, we’ll build something real: a small HTTP server that talks to a database.
The goal is to create a service that syncs orders from an API (like Magento) into a local database. That’s where Go starts to shine.
This is part one of a series on Go for JavaScript developers. Next up: building your first HTTP server and connecting to a database.
Code examples: All code is copy-paste ready. If something doesn’t work, let me know.
Related Articles
- Compressed NFTs: Collections, Verification, and Building a Claim Page
Taking our cNFT minting system to production: creating verified collections, building a web-based claim flow, and preparing for mainnet deployment.
- SQLite Basics: Learn to Write and Query a Database with Bun
A practical introduction to SQLite using Bun's built-in database—create tables, insert data, and write queries from scratch.
- Building Compressed NFTs on Solana with Generative SVG Art
A practical guide to creating and minting compressed NFTs (cNFTs) on Solana using Metaplex Bubblegum, with animated SVG artwork generated from wallet addresses.