NanoQR is a high-performance, headless QR code generator service built with Go.
I created it because I thought the idea was quite fun and an interesting project: an (almost) over engineered headless api qr generator and extensible software for multiple handlers.
Designed to be lightweight, and easy to self-host (plug and play), NanoQR provides a clean API to generate QR codes as Base64-encoded PNGs (or any other encoding you want).
There's also a sample website!: https://nanoqr-web.vercel.app/
- ⚡ High Performance: Built 100% in Go for minimal latency.
- 🐳 Docker Ready: Multi-stage Dockerfile optimized for Alpine Linux.
- 🛠 Modular Design: Decoupled architecture using interfaces, making it easy to swap handlers for when there are more (CLI, gRPC, etc.).
- 📦 Zero Bloat: Headless by design—perfect for microservices.
.
├── cmd/
│ └── api/
│ └── main.go # Application entry point
├── internal/
│ ├── handlers/
│ │ └── http_handler.go # REST API transport
│ ├── model/
│ │ └── model.go # Data transfer objects (JSON)
│ └── service/ # Core business logic
│ ├── qr_service.go # Domain interfaces
│ └── default_qr_service.go # Core QR generation implementation
├── Dockerfile # Minimal production image
├── go.mod
├── go.sum
└── README.md
To try it out quickly, you can run the binary directly with go in your terminal
go run cmd/api/main.goOr
docker build -t nanoqr .
docker run -p 8080:8080 nanoqrThe QR generator service will be listening at: http://localhost:8080/api/qr
➡️ For a quick test in terminal, you can run (linux):
curl -X POST http://localhost:8080/api/qr \
-H "Content-Type: application/json" \
-d '{"input": "https://google.com", "size": 256, "recoverLevel": "medium"}'\➡️ For windows (cmd):
curl -X POST http://localhost:8080/api/qr -H "Content-Type: application/json" -d "{\"input\": \"https://google.com\", \"size\": 256, \"recoverLevel\": \"medium\"}" Endpoint: POST /api/qr
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
input |
string |
Yes | - | URL or anything to encode. |
size |
int |
No | 256 |
Image size (41px to 2048px). |
recoverLevel |
string |
No | medium |
Error correction: low, medium, high, highest. |
{
"input": "https://github.com/",
"size": 512,
"recoverLevel": "high"
}{
"info": {
"input": "https://github.com/",
"size": 512,
"recoverLevel": "high"
},
"qr": "iVBORw0KGgoAAAANSUhEUgAA...",
"status": "success"
}NanoQR includes a CORS middleware to allow secure cross-origin requests from your web applications.
First, the Origin header of incoming requests (outside of the middleware) is checked:
var allowedOrigins = []string {
"https://nanoqr-web.vercel.app",
"http://localhost:5173",
"https://another-web.com",
}
func isOriginAllowed(origin string) bool {
for _, o := range allowedOrigins {
if o == origin {
return true
}
}
return false
}The URLs are examples; simply replace them.
- If the origin is allowed, it sets the headers.
- For preflight (OPTIONS) requests, it responds with status 204 and the appropriate headers, without invoking the main handler.
- For POST requests, it allows the request to proceed to the QR handler as usual.
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Add CORS headers
origin := r.Header.Get("Origin")
if isOriginAllowed(origin) {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
w.Header().Set("Content-Type", "application/json")
}
// Preflight
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusNoContent)
return
}
// Call next handler (QRhandler)
next.ServeHTTP(w, r)
})
}NanoQR uses a QRService interface, allowing you to build your own handlers (CLI, Lambda, gRPC) effortlessly.
type QRService interface {
Generate(input string, size int, recoverLevel string) ([]byte, error)
}You can inject the service into your functions to maintain testability and flexibility:
func CLIHandler(service QRService, input string) {
qr, err := service.Generate(input, 256, "medium")
if err != nil {
log.Fatal(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(qr))
}
// Usage
qrService := &DefaultQRService{}
CLIHandler(qrService, "https://google.com")Contributions are what make the open-source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
Distributed under the MIT License.