If you've been following these tutorials, you've got a handle on the language, the file system, and even the madness of concurrency. Now, we're going to drag our code onto the web. It's the logical next step, though I'll be the first to admit that exposing your code to the internet is a bit like letting a toddler loose in a china shop—things are going to get interesting, and possibly very loud.
The Router: Keeping things in order
We keep our web logic clean by using a router. It takes the incoming noise of HTTP requests and points them where they need to go. A Handler is essentially just a function that knows how to talk to a ResponseWriter.
module main
import "std/web/router"
import "std/web/server"
import "std/web/types"
fn home(req: types.Request, w: *types.ResponseWriter) {
w.Write("Hello, Skink!")
}
fn greet(req: types.Request, w: *types.ResponseWriter) {
// Routing params let us capture bits of the path
name := req.URLParam("name")
w.Write("Hello, {name}!")
}
fn jsonDemo(req: types.Request, w: *types.ResponseWriter) {
// JSON is the universal tongue of the web
w.JSON(200, "{\"ok\":true}")
}
Starting the engine
The server doesn't just run; you have to point it at a port and a router.
fn main() -> int {
r := router.NewRouter()
r.Get("/", home)
r.Get("/hello/:name", greet)
r.Get("/api/status", jsonDemo)
s := server.NewServer(8080, &r)
err := s.ListenAndServe()
if err.message != "" {
print("server error: {err.message}")
return 1
}
return 0
}
The Request: Your window into the madness
The Request object is your primary interface to the outside world. It tells you exactly what the user is throwing at you—headers, query parameters, and, if they're particularly pushy, the body content.
fn search(req: types.Request, w: *types.ResponseWriter) {
q := req.QueryParam("q")
w.Write("You searched for: {q}")
}
Middleware: The gatekeeper
If you need to log requests, authorize users, or just add some headers, don't mess up your business logic. Use middleware. It wraps your handlers, letting you execute code before or after the "real work" happens.
fn logging(next: types.Handler) -> types.Handler {
return fn(req: types.Request, w: *types.ResponseWriter) {
print("{req.Method()} {req.Path()}")
next(req, w)
}
}
Exercises
Don't just launch a server and wait for the internet to start screaming at you; test it properly:
- The Echo Chamber: Write a POST /echo handler that reads req.Body() and sends the exact same text back. It's the "Hello, World" of networking.
- The Dynamic User: Implement a /user/:id route that returns a JSON object containing that ID.
- The Local Test: Use curl to hit your server. If you don't have curl, well... you should probably get it. It’s the Swiss Army knife of the web.
Next
When you're ready to start storing real, persistent data rather than just JSON files, we'll talk about Working with SQLite.