Your First Application
5 minute read
Build a simple REST API to learn Rivaas basics. You’ll create a working application with multiple routes, JSON responses, and graceful shutdown.
Create Your Project
Create a new directory and initialize a Go module:
mkdir hello-rivaas
cd hello-rivaas
go mod init example.com/hello-rivaas
Install Rivaas
go get rivaas.dev/app
Write Your Application
Create a file named main.go:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"rivaas.dev/app"
)
func main() {
// Create a new Rivaas application
a := app.MustNew(
app.WithServiceName("hello-rivaas"),
app.WithServiceVersion("v1.0.0"),
)
// Define routes
a.GET("/", handleRoot)
a.GET("/hello/:name", handleHello)
a.POST("/echo", handleEcho)
// Setup graceful shutdown
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
// Start the server
log.Println("🚀 Starting server on http://localhost:8080")
if err := a.Start(ctx, ":8080"); err != nil {
log.Fatal(err)
}
}
// handleRoot returns a welcome message
func handleRoot(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{
"message": "Welcome to Rivaas!",
"version": "v1.0.0",
})
}
// handleHello greets a user by name
func handleHello(c *app.Context) {
name := c.Param("name")
c.JSON(http.StatusOK, map[string]string{
"message": "Hello, " + name + "!",
})
}
// handleEcho echoes back the request body
func handleEcho(c *app.Context) {
var body map[string]any
if err := c.Bind(&body); err != nil {
c.JSON(http.StatusBadRequest, map[string]string{
"error": "Invalid JSON",
})
return
}
c.JSON(http.StatusOK, map[string]any{
"echo": body,
})
}
Run Your Application
Start the server:
go run main.go
You should see output like:
🚀 Starting server on http://localhost:8080
Test Your API
Open a new terminal and test the endpoints:
Test the root endpoint
curl http://localhost:8080/
Response:
{
"message": "Welcome to Rivaas!",
"version": "v1.0.0"
}
Test the greeting endpoint
curl http://localhost:8080/hello/World
Response:
{
"message": "Hello, World!"
}
Test the echo endpoint
curl -X POST http://localhost:8080/echo \
-H "Content-Type: application/json" \
-d '{"name": "Rivaas", "type": "framework"}'
Response:
{
"echo": {
"name": "Rivaas",
"type": "framework"
}
}
Understanding the Code
Here’s what each part does:
1. Creating the Application
a := app.MustNew(
app.WithServiceName("hello-rivaas"),
app.WithServiceVersion("v1.0.0"),
)
MustNew()creates a new application. Panics on error. Use inmain()functions.WithServiceName()sets the service name.WithServiceVersion()sets the version.
2. Defining Routes
a.GET("/", handleRoot)
a.GET("/hello/:name", handleHello)
a.POST("/echo", handleEcho)
GET()andPOST()register route handlers.:nameis a path parameter. Access it withc.Param("name").- Handler functions receive an
*app.Contextwith all request data.
3. Graceful Shutdown
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
if err := a.Start(ctx, ":8080"); err != nil {
log.Fatal(err)
}
signal.NotifyContext()creates a context that cancels on SIGINT (Ctrl+C) or SIGTERM.Start()starts the server and blocks until the context is canceled.- The server shuts down gracefully. It finishes active requests before stopping.
4. Handler Functions
func handleRoot(c *app.Context) {
c.JSON(http.StatusOK, map[string]string{
"message": "Welcome to Rivaas!",
})
}
- Handlers receive an
*app.Context. c.JSON()sends a JSON response.c.Param()gets path parameters.c.Bind()parses request bodies. It auto-detects JSON, form, and other formats.
Common Patterns
Path Parameters
// Route: /users/:id/posts/:postId
a.GET("/users/:id/posts/:postId", func(c *app.Context) {
userID := c.Param("id")
postID := c.Param("postId")
c.JSON(http.StatusOK, map[string]string{
"user_id": userID,
"post_id": postID,
})
})
Query Parameters
// Route: /search?q=rivaas&limit=10
a.GET("/search", func(c *app.Context) {
query := c.Query("q")
limit := c.QueryDefault("limit", "20")
c.JSON(http.StatusOK, map[string]string{
"query": query,
"limit": limit,
})
})
Request Headers
a.GET("/headers", func(c *app.Context) {
userAgent := c.Request.Header.Get("User-Agent")
c.JSON(http.StatusOK, map[string]string{
"user_agent": userAgent,
})
})
Different Status Codes
a.GET("/not-found", func(c *app.Context) {
c.JSON(http.StatusNotFound, map[string]string{
"error": "Resource not found",
})
})
a.POST("/created", func(c *app.Context) {
c.JSON(http.StatusCreated, map[string]string{
"message": "Resource created",
})
})
Testing Your Application
Rivaas provides testing utilities for integration tests:
package main
import (
"net/http"
"net/http/httptest"
"testing"
"rivaas.dev/app"
)
func TestHelloEndpoint(t *testing.T) {
// Create test app
a, err := app.New()
if err != nil {
t.Fatalf("Failed to create app: %v", err)
}
a.GET("/hello/:name", handleHello)
// Create test request
req := httptest.NewRequest(http.MethodGet, "/hello/Gopher", nil)
// Test the request
resp, err := a.Test(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode != http.StatusOK {
t.Errorf("Expected status 200, got %d", resp.StatusCode)
}
}
Key Testing Methods:
a.Test(req)- Execute a request without starting the servera.TestJSON(method, path, body)- Test JSON endpointsapp.ExpectJSON(t, resp, status, target)- Verify JSON responses
See the blog example for comprehensive testing patterns.
Common Mistakes
Forgetting Error Handling
// ❌ Bad: Ignoring errors
a := app.MustNew() // Panics on error
// ✅ Good: Handle errors properly
a, err := app.New()
if err != nil {
log.Fatalf("Failed to create app: %v", err)
}
Not Using Context for Shutdown
// ❌ Bad: No graceful shutdown
a.Start(context.Background(), ":8080")
// ✅ Good: Graceful shutdown with signals
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()
a.Start(ctx, ":8080")
Registering Routes After Start
// ❌ Bad: Routes registered after Start
a.Start(ctx, ":8080")
a.GET("/late", handler) // Won't work!
// ✅ Good: Routes before Start
a.GET("/early", handler)
a.Start(ctx, ":8080")
Production Basics
Before deploying your first application:
- ✅ Use environment-based configuration (see Configuration)
- ✅ Add health endpoints for Kubernetes/Docker
- ✅ Enable structured logging
- ✅ Set appropriate timeouts
- ✅ Add recovery middleware (included by default)
Quick Production Setup:
a, err := app.New(
app.WithServiceName("my-api"),
app.WithServiceVersion("v1.0.0"),
app.WithEnvironment("production"),
app.WithHealthEndpoints(
app.WithReadinessCheck("ready", func(ctx context.Context) error {
return nil // Add real checks here
}),
),
)
See the full-featured example for production patterns.
What’s Next?
You now have a working Rivaas application. Here are the next steps:
- Configuration — Learn configuration options
- Middleware — Add middleware for logging, CORS, etc.
- Routing Guide — Advanced routing patterns
- Observability Guide — Add logging, metrics, and tracing
Complete Example
The complete code is available in the examples repository.
Troubleshooting
Port Already in Use
If you see “address already in use”:
# Find what's using port 8080
lsof -i :8080
# Kill the process or use a different port
Change the port in your code:
a.Start(ctx, ":3000") // Use port 3000 instead
JSON Binding Errors
If Bind() fails for JSON requests, ensure:
- Content-Type header is set to
application/json - Request body contains valid JSON
- JSON structure matches your Go struct
Ready to learn more? Continue to Configuration →
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.