Skip to content

Hangell/jano

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hangell/jano Go Report Card Contributing License Go Reference

Jano is a Go library that allows you to create HTTP servers with routing similar to Express.js.

Installation

go get github.com/Hangell/jano

Usage Example

package main

import (
    "log"
    "net/http"
    "os"
    "github.com/Hangell/jano"
)

func main() {
    app := jano.New()

    app.Use(loggingMiddleware)

    app.Get("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Welcome to Jano!"))
    })

    app.Post("/login", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Login successful!"))
    })

    app.NotFound(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Custom 404: Page not found"))
    })

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    log.Printf("Listening on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, app.Router()))
}

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
    })
}

Features

  • Support for multiple HTTP methods (GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD)
  • Middleware with the Use function
  • Simple and intuitive routing
  • Support for custom 404 handlers with NotFound

Project Structure

jano/
│   bench_test.go
│   go.mod
│   jano.go
│   README.md
│
├───.idea
│   │   .gitignore
│   │   jano.iml
│   │   modules.xml
│   │   workspace.xml
│
└───examples
    └───api
        │   main.go
        │   requests.http
        │
        ├───handlers
        │       person_handlers.go
        │
        └───routes
                routes.go

Example API

Handlers (examples/api/handlers/person_handlers.go)

package handlers

import (
    "encoding/json"
    "net/http"
    "strconv"
    "sync"
)

type Person struct {
    ID      int    `json:"id"`
    Name    string `json:"name"`
    Age     int    `json:"age"`
    Student bool   `json:"student"`
}

var (
    people   = make(map[int]Person)
    idCounter = 1
    mutex     = &sync.Mutex{}
)

func GetPeople(w http.ResponseWriter, r *http.Request) {
    mutex.Lock()
    defer mutex.Unlock()
    var peopleList []Person
    for _, person := range people {
        peopleList = append(peopleList, person)
    }
    json.NewEncoder(w).Encode(peopleList)
}

func CreatePerson(w http.ResponseWriter, r *http.Request) {
    var person Person
    if err := json.NewDecoder(r.Body).Decode(&person); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    mutex.Lock()
    defer mutex.Unlock()
    person.ID = idCounter
    idCounter++
    people[person.ID] = person
    w.WriteHeader(http.StatusCreated)
    json.NewEncoder(w).Encode(person)
}

func GetPerson(w http.ResponseWriter, r *http.Request) {
    idStr := r.Context().Value("id").(string)
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid person ID", http.StatusBadRequest)
        return
    }
    mutex.Lock()
    defer mutex.Unlock()
    person, ok := people[id]
    if !ok {
        http.Error(w, "Person not found", http.StatusNotFound)
        return
    }
    json.NewEncoder(w).Encode(person)
}

func UpdatePerson(w http.ResponseWriter, r *http.Request) {
    idStr := r.Context().Value("id").(string)
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid person ID", http.StatusBadRequest)
        return
    }
    var person Person
    if err := json.NewDecoder(r.Body).Decode(&person); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    mutex.Lock()
    defer mutex.Unlock()
    _, ok := people[id]
    if !ok {
        http.Error(w, "Person not found", http.StatusNotFound)
        return
    }
    person.ID = id
    people[id] = person
    json.NewEncoder(w).Encode(person)
}

func DeletePerson(w http.ResponseWriter, r *http.Request) {
    idStr := r.Context().Value("id").(string)
    id, err := strconv.Atoi(idStr)
    if err != nil {
        http.Error(w, "Invalid person ID", http.StatusBadRequest)
        return
    }
    mutex.Lock()
    defer mutex.Unlock()
    if _, ok := people[id]; !ok {
        http.Error(w, "Person not found", http.StatusNotFound)
        return
    }
    delete(people, id)
    w.WriteHeader(http.StatusNoContent)
}

Routes (examples/api/routes/routes.go)

package routes

import (
	"log"
	"net/http"
	"github.com/hangell/jano"
	"your_project/handlers"  // Replace with your project path
)

func SetupRoutes(app *jano.Jano) {
	app.Use(loggingMiddleware)
	app.Use(authenticationMiddleware)
	app.Use(corsMiddleware)

	app.Get("/people", handlers.GetPeople)
	app.Post("/people", handlers.CreatePerson)
	app.Get("/people/{id}", handlers.GetPerson)
	app.Put("/people/{id}", handlers.UpdatePerson)
	app.Delete("/people/{id}", handlers.DeletePerson)

	app.NotFound(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Custom 404: Page not found"))
	})
}

func loggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s", r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	})
}

func authenticationMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Example: Check for a specific header or token
		if r.Header.Get("X-Auth-Token") != "secret-token" {
			http.Error(w, "Forbidden", http.StatusForbidden)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func corsMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if r.Method == "OPTIONS" {
			w.WriteHeader(http.StatusOK)
			return
		}
		next.ServeHTTP(w, r)
	})
}

Main (examples/api/main.go)

package main

import (
    "log"
    "net/http"
    "os"
    "github.com/hangell/jano"
    "your_project/routes"  // Replace with your project path
)

func main() {
    app := jano.New()

    routes.SetupRoutes(app)

    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    log.Printf("Listening on port %s", port)
    log.Fatal(http.ListenAndServe(":"+port, app.Router()))
}

HTTP Requests (examples/api/requests.http)

### Get all people
GET http://localhost:8080/people
Accept: application/json

### Create a new person
POST http://localhost:8080/people
Content-Type: application/json

{
    "name": "John Doe",
    "age": 30,
    "student": false
}

### Get a specific person by ID
GET http://localhost:8080/people/1
Accept: application/json

### Update a person
PUT http://localhost:8080/people/1
Content-Type: application/json

{
    "name": "John Smith",
    "age": 31,
    "student": true
}

### Delete a person
DELETE http://localhost:8080/people/1

Benchmark Results

The following are the results from running the benchmarks:

Benchmark Operations (ops) Time per Operation (ns/op) Memory Allocated (B/op) Allocations per Operation (allocs/op)
BenchmarkJano-8 1,566,112 744.9 800 8
BenchmarkJanoSimple-8 2,936,114 410.4 400 4
BenchmarkJanoAlternativeInRegexp-8 752,006 1,478 1,600 16
BenchmarkManyPathVariables-8 538,432 2,160 1,566 25

##Curiosities

The name "Jano" is inspired by Janus, the Roman god of changes and transitions. Janus is often depicted with two faces looking in opposite directions, symbolizing endings and beginnings, the past and the future. This symbolism aligns with the Jano library's goal of facilitating smooth transitions and handling changes in HTTP routing efficiently.

License

This project is licensed under the BSD-3-Clause License - see the LICENSE file for details.

Donations

If you enjoyed using this project, please consider making a donation to support the continuous development of the project. You can make a donation using one of the following options:

  • Cryptocurrencies or NFT MetaMask: 0xEd4d1be72F807Faa358C966a8eF63367c200130F

Rodrigo Rangel

Releases

No releases published

Packages

No packages published

Languages