Document not found (404)
-This URL is invalid, sorry. Please use the navigation bar or search to continue.
- -diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index 3571d19..5ab77ee 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -1,4 +1,4 @@ -name: Go CI & Docs Deployment +name: Go CI & Site Deployment on: push: @@ -40,8 +40,8 @@ jobs: - name: Run Go Tests run: go test -v ./... - deploy-docs: - name: Build and Deploy Documentation + deploy-site: + name: Build and Deploy Site runs-on: ubuntu-latest needs: lint-test if: github.event_name == 'push' && github.ref == 'refs/heads/main' @@ -66,44 +66,10 @@ jobs: go install github.com/xlc-dev/nova@latest echo "$(go env GOPATH)/bin" >> $GITHUB_PATH - - name: Setup Rust and mdBook - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - name: Cache mdBook and mdbook-mermaid - uses: actions/cache@v4 - with: - path: | - ~/.cargo/bin/mdbook - ~/.cargo/bin/mdbook-mermaid - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}-mdbook-mermaid - - name: Install mdBook - run: | - if ! command -v mdbook &> /dev/null - then - cargo install mdbook --locked - fi - - name: Install mdbook-mermaid - run: | - if ! command -v mdbook-mermaid &> /dev/null - then - cargo install mdbook-mermaid --locked - fi - - - name: Generate Nova Documentation - run: | - echo "Running nova gendoc..." - nova gendoc - echo "Nova docs generated." - - - name: Build mdBook + - name: Run fssg run: | - echo "Building mdBook..." - cd docs - mdbook build - cd .. - echo "mdBook build complete." + cd ./www + ./fssg -n -j 50 - name: Setup Pages uses: actions/configure-pages@v5 @@ -111,7 +77,7 @@ jobs: - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: - path: "./docs/" + path: "./www/dist/" - name: Deploy to GitHub Pages id: deployment diff --git a/.gitignore b/.gitignore index ee65dbe..e08167f 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ Thumbs.db *.venv *.db novarun +dist/ diff --git a/Makefile b/Makefile deleted file mode 100644 index d66d34a..0000000 --- a/Makefile +++ /dev/null @@ -1,30 +0,0 @@ -BINARY_NAME=novarun - -.PHONY: build clean fmt test docs help -default: build - -build: - @go build -o $(BINARY_NAME) - @cd docs && RUST_LOG=error mdbook build - -clean: - @rm -f $(BINARY_NAME) - -fmt: - @goimports -w . - @go fmt ./... - -test: - @go test ./... -v - -docs: - @cd docs && mdbook serve --open - -help: - @echo "Available Make targets:" - @echo " build : Build the Go application (default)" - @echo " clean : Remove the built binary ($(BINARY_NAME))" - @echo " fmt : Format Go source code (using goimports)" - @echo " test : Run Go tests" - @echo " docs : Serve the documentation site locally" - @echo " help : Show this help message" diff --git a/README.md b/README.md index 93db149..a21a34d 100644 --- a/README.md +++ b/README.md @@ -29,22 +29,21 @@ Nova consists of two main components: - **The Nova Binary:** A command-line tool (installed via `go install`) that handles project generation, scaffolding, and database migrations. It's perfect for setting up new projects quickly and managing some boilerplate. - **The Nova Library:** The library that provides the functionality for building web applications, including REST APIs, middleware, and more. Your generated projects with the binary import this library. -## ✨ Features +## Features -- 🛠️ **CLI Tooling:** Integrated command-line tooling to build any CLI for your application. -- 🏗️ **Project Scaffolding:** Quickly generate new projects with a sensible default structure using `nova new`. -- 🗃️ **Database Migrations:** Manage database migrations effortlessly with the `nova` binary. -- 🛠️ **Streamlined REST APIs:** Simplified routing, request handling, and response generation. -- 🚧 **Validation & OpenAPI:** Built-in support for request validation and OpenAPI (Swagger) spec generation. -- 🧩 **Middleware Support:** Easily add and manage middleware for enhanced functionality. -- 📄 **Templating Engine:** Built-in support for building HTML templates within Go files. +- **CLI Tooling:** Integrated command-line tooling to build any CLI for your application. +- **Project Scaffolding:** Quickly generate new projects with a sensible default structure using `nova new`. +- **Database Migrations:** Manage database migrations effortlessly with the `nova` binary. +- **Streamlined REST APIs:** Simplified routing, request handling, and response generation. +- **Validation & OpenAPI:** Built-in support for request validation and OpenAPI (Swagger) spec generation. +- **Middleware Support:** Easily add and manage middleware for enhanced functionality. +- **Templating Engine:** Built-in support for building HTML templates within Go files. -## 🚀 Getting Started +## Getting Started ### Prerequisites - Go 1.23 or later -- Make (optional, does _make_ life easy ;) – pun intended) ### Installation @@ -74,29 +73,63 @@ go install github.com/xlc-dev/nova@latest ```sh # Build the binary - go build - - # Or, if you enabled Makefile during `nova new` - # make build + go build -o novarun # Run the application - ./myproject + ./novarun ``` Your application should now be running on `http://localhost:8080`. From here, you can explore the library's features like REST APIs and middleware. -## 📚 Documentation +### Common Go Commands + +Here are some common Go commands you might use for development: + +- **Build the application:** + + ```sh + go build -o novarun + ``` + + This command compiles your Go application and creates an executable named `novarun` in the current directory. + +- **Run the application:** + + ```sh + ./novarun + ``` + + Executes the built application. + +- **Format Go source code:** + + ```sh + goimports -w . + go fmt ./... + ``` + + These commands format your Go code according to Go's standard style. `goimports` also adds/removes necessary imports. + +- **Run tests:** + ```sh + go test ./... -v + ``` + This command runs all tests in your project and its subdirectories, with verbose output. + +## Documentation -Documentation is available to guide you through Nova's features and usage, including how the binary and library work together. The docs are created and built using [mdBook](https://github.com/rust-lang/mdBook). All documentation can be found in the `docs/src` folder written in Markdown. +Documentation is available to guide you through Nova's features and usage. The docs are created and built using [ffsg](https://xlc-dev.github.io/fssg/). +A fast static site generator written by yours truely. +All documentation can be found in the `docs/src` folder written in Markdown. -➡️ **[Read the full documentation here](https://xlc-dev.github.io/nova/book)** +**[Read the full documentation here](https://xlc-dev.github.io/nova/docs)** -## 🤝 Contributing +## Contributing Contributions are welcome! Whether it's bug reports, feature requests, documentation improvements, or code contributions, please feel free to open an issue or submit a pull request. Please read the [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines. -## 📜 License +## License This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..911aabd --- /dev/null +++ b/doc.go @@ -0,0 +1,6 @@ +/* +Package main implements a Fullstack HTTP Web Framework called Nova. + +See https://xlc-dev.github.io/nova for more information about Nova. +*/ +package main // import "github.com/xlc-dev/nova/nova" diff --git a/docs/book.toml b/docs/book.toml deleted file mode 100644 index 9da2342..0000000 --- a/docs/book.toml +++ /dev/null @@ -1,33 +0,0 @@ -[book] -language = "en" -multilingual = false -src = "src" -title = "nova" - -[output.html] -default-theme = "navy" -preferred-dark-theme = "navy" -smart-punctuation = true -mathjax-support = true -additional-js = ["mermaid.min.js", "mermaid-init.js"] - -[output.html.print] -enable = false - -[output.html.fold] -enable = true -level = 0 - -[output.html.search] -limit-results = 20 -use-boolean-and = true -boost-title = 2 -boost-hierarchy = 2 -boost-paragraph = 1 -expand = true -heading-split-level = 2 - -[preprocessor] - -[preprocessor.mermaid] -command = "mdbook-mermaid" diff --git a/docs/book/.nojekyll b/docs/book/.nojekyll deleted file mode 100644 index f173110..0000000 --- a/docs/book/.nojekyll +++ /dev/null @@ -1 +0,0 @@ -This file makes sure that Github Pages doesn't process mdBook's output. diff --git a/docs/book/404.html b/docs/book/404.html deleted file mode 100644 index b9cdb48..0000000 --- a/docs/book/404.html +++ /dev/null @@ -1,219 +0,0 @@ - - - -
- - -This URL is invalid, sorry. Please use the navigation bar or search to continue.
- -Nova has a built in CLI feature. It simplifies common CLI tasks by providing:
-string, int, bool, float64, and []string flags.help command.--version and -v global flags.Context object providing access to parsed flags, arguments, and application metadata.NewCLI validates your configuration for conflicts (e.g., reserved names) before running.Here’s a minimal example to get you started:
-package main
-
-import (
- "fmt"
- "log"
- "os"
-
- "github.com/xlc-dev/nova/nova"
-)
-
-func main() {
- // Define the application structure
- app := &nova.CLI{
- Name: "myapp",
- Version: "1.0.0",
- Description: "A simple example CLI application.",
- Commands: []*nova.Command{
- {
- Name: "greet",
- Usage: "Prints a friendly greeting",
- Action: greetAction, // Function to execute
- },
- },
- // Optional: Define a default action if no command is given
- // Action: defaultAction,
- }
-
- // Validate the CLI configuration and initialize internal flags.
- // It's crucial to use NewCLI before Run.
- cli, err := nova.NewCLI(app)
- if err != nil {
- log.Fatalf("Failed to initialize CLI: %v", err)
- }
-
- // Run the application, passing command-line arguments
- if err := cli.Run(os.Args); err != nil {
- // Errors from parsing, validation, or the action itself are returned here.
- log.Fatalf("Error running CLI: %v", err)
- }
-}
-
-// greetAction is the function executed by the 'greet' command.
-// It receives a Context object.
-func greetAction(ctx *nova.Context) error {
- fmt.Println("Hello from Nova!")
- // You can access flags and arguments via ctx here if needed
- return nil // Return nil on success, or an error
-}
-
-/*
-// Optional default action
-func defaultAction(ctx *nova.Context) error {
- fmt.Println("Running default action. Use 'myapp help' for commands.")
- return nil
-}
-*/
-
-Build and run:
-go build -o myapp
-./myapp greet
-# Output: Hello from Nova!
-
-./myapp --version
-# Output: myapp version 1.0.0
-
-./myapp help
-# Output: Shows application help
-
-./myapp help greet
-# Output: Shows help specific to the greet command
-
-CLI StructThe nova.CLI struct is the root of your application. It holds metadata and configuration:
Name (string, Required): The name of your application, used in help messages.Version (string, Required): The application version, displayed by the --version flag.Description (string): A short description of the application, shown in the main help.Commands ([]*Command): A slice of commands the application supports.Action (ActionFunc): A function to run if no command is specified on the command line. If nil and no command is given, the main help is shown.GlobalFlags ([]Flag): Flags that apply to the application globally, regardless of the command being run. Parsed before command flags.Authors (string): Optional author information, displayed in the main help.Important: Always initialize your application using nova.NewCLI(app). This function validates your configuration (checking for required fields, reserved names like help for commands or version for global flags) and sets up internal flags before you call Run.
Command StructThe nova.Command struct defines a specific action your application can perform.
Name (string, Required): The primary name used to invoke the command. Cannot be help.Aliases ([]string): Alternative names for the command. Cannot include h.Usage (string): A short, one-line description shown in the main command list.Description (string): A more detailed description shown in the command’s specific help (myapp help <command>).ArgsUsage (string): Describes the expected arguments (e.g., <input> [output]), shown in the command’s help.Action (ActionFunc, Required): The function to execute when the command is invoked. It receives a *nova.Context.Flags ([]Flag): Flags specific to this command. Cannot be named help or have an alias h.Flag Interfacenova.Flag is an interface implemented by concrete flag types. You don’t use the interface directly but rather the specific types:
nova.StringFlagnova.IntFlagnova.BoolFlagnova.Float64Flagnova.StringSliceFlagEach flag type defines how a command-line option is parsed and stored. Key properties include Name, Aliases, Usage, Default, and Required.
Context ObjectAn instance of nova.Context is passed to every ActionFunc. It provides access to runtime information:
CLI and the currently executing Command.Run)When you call cli.Run(os.Args):
--version/-v). If --version is found, it prints the version and exits.help command).help command, its action is run. Otherwise, the command’s specific flags are parsed and validated. The command’s ActionFunc is then executed with a Context containing command flags, global flags, and remaining arguments.CLI.Action Defined: The global ActionFunc is executed with a Context containing global flags and all non-flag arguments.CLI.Action: If arguments were provided but didn’t match a command, an “unknown command” error is returned. If no arguments were provided, the main application help is displayed.Run.Define commands by creating instances of nova.Command and assigning them to the CLI.Commands slice.
// Action function for the 'create' command
-func createAction(ctx *nova.Context) error {
- // Access flags and args via ctx
- resourceName := ctx.Args()[0] // Example: Get first argument
- fmt.Printf("Creating resource: %s\n", resourceName)
- return nil
-}
-
-// Command definition
-var createCmd = &nova.Command{
- Name: "create", // Primary name (e.g., `./myapp create`)
- Aliases: []string{"c"}, // Aliases (e.g., `./myapp c`)
- Usage: "Create a new resource", // Short help text
- Description: `Creates a new resource based on the provided name.
-This command demonstrates basic command structure.`, // Long help text
- ArgsUsage: "<resource-name>", // Argument syntax help
- Action: createAction, // Function to run
- Flags: []nova.Flag{ /* Command-specific flags go here */ },
-}
-
-// In main():
-app := &nova.CLI{
- // ... other CLI fields
- Commands: []*nova.Command{
- createCmd,
- // ... other commands
- },
-}
-
-Reserved Names: Remember, command Name cannot be help, and Aliases cannot include h.
Flags provide options and configuration for your application and commands.
-Nova provides the following concrete flag types, all implementing the nova.Flag interface:
| Type | Go Type | Example Usage | Description |
|---|---|---|---|
StringFlag | string | --name="John Doe" | Accepts a text value. |
IntFlag | int | --port=8080 | Accepts an integer value. |
BoolFlag | bool | --verbose | Acts as a switch (true if present). |
Float64Flag | float64 | --ratio=1.5 | Accepts a floating-point number. |
StringSliceFlag | []string | --tag foo --tag bar | Accepts multiple string values (repeatable). |
Define flags by creating instances of the flag types and assigning them to CLI.GlobalFlags or Command.Flags.
// StringFlag Example
-&nova.StringFlag{
- Name: "output", // Long name: --output
- Aliases: []string{"o"}, // Short name: -o
- Usage: "Specify the output file path", // Help text
- Default: "stdout", // Default value if flag not provided
- Required: false, // The flag is optional
-},
-
-// BoolFlag Example
-&nova.BoolFlag{
- Name: "verbose",
- Aliases: []string{"V"}, // Note: -v is reserved globally for version
- Usage: "Enable verbose logging",
- Default: false,
-},
-
-// IntFlag Example
-&nova.IntFlag{
- Name: "retries",
- Usage: "Number of times to retry",
- Default: 3,
- Required: true, // This flag must be provided
-},
-
-// Float64Flag Example
-&nova.Float64Flag{
- Name: "ratio",
- Aliases: []string{"r"},
- Usage: "A floating-point value representing a ratio",
- Default: 1.0,
- Required: false,
-},
-
-// StringSliceFlag Example
-&nova.StringSliceFlag{
- Name: "tag",
- Aliases: []string{"t"},
- Usage: "Add one or more tags (can be specified multiple times)",
- Default: []string{}, // Defaults to an empty slice if flag not provided
- Required: false,
-},
-
-CLI.GlobalFlags. They are available and parsed before any command is run. Useful for options like --config, --verbose, or --region.
-Name: "version" or Aliases: []string{"v"}.Command.Flags. They are only available and parsed when that specific command is invoked.
-Name: "help" or Aliases: []string{"h"}.app := &nova.CLI{
- // ...
- GlobalFlags: []nova.Flag{
- &nova.BoolFlag{
- Name: "verbose",
- Usage: "Enable verbose output globally",
- },
- },
- Commands: []*nova.Command{
- {
- Name: "serve",
- Usage: "Start a server",
- Action: serveAction,
- Flags: []nova.Flag{
- &nova.IntFlag{
- Name: "port",
- Aliases: []string{"p"},
- Usage: "Port to listen on",
- Default: 8080,
- },
- },
- },
- },
-}
-
-Set Required: true in the flag definition. Nova automatically checks if required flags were provided during the Run process after parsing. If a required flag is missing, Run returns an error. This applies to all flag types except BoolFlag. For StringSliceFlag, it ensures the flag was provided at least once.
&nova.StringFlag{
- Name: "api-key",
- Usage: "Your API key for authentication",
- Required: true, // ./myapp command --api-key=XYZ (Required)
-},
-
-Set the Default field in the flag definition. If the user provides the flag, the user’s value overrides the default.
&nova.IntFlag{
- Name: "timeout",
- Usage: "Request timeout in seconds",
- Default: 30, // Defaults to 30 if --timeout is not specified
-},
-
-The Aliases field ([]string) provides alternative names for flags.
-a).--alias).Nova handles the prefixing automatically based on the alias length when generating help text.
-&nova.StringFlag{
- Name: "file", // --file
- Aliases: []string{"f"}, // -f
- Usage: "Input filename",
-},
-
-StringSliceFlag allows a flag to be specified multiple times, collecting all values into a []string.
&nova.StringSliceFlag{
- Name: "tag", // --tag
- Aliases: []string{"t"}, // -t
- Usage: "Add a tag (can be specified multiple times)",
- Default: []string{"default-tag"}, // Optional default slice
- Required: false,
-},
-
-Usage: ./myapp process --tag production -t us-east-1 --tag webserver
-In the action, ctx.StringSlice("tag") would return []string{"production", "us-east-1", "webserver"}.
The ActionFunc receives a *nova.Context pointer, which is your gateway to runtime information.
func myAction(ctx *nova.Context) error {
- // Accessing Arguments
- // Args() returns positional arguments AFTER flags have been parsed.
- args := ctx.Args() // Type: []string
- if len(args) > 0 {
- fmt.Printf("First argument: %s\n", args[0])
- }
-
- // Accessing Flag Values
- // Use type-specific methods. Nova checks command flags first, then global flags.
- // Returns the zero value for the type if the flag wasn't found or type mismatches.
- configFile := ctx.String("config") // Checks command's --config, then global --config
- port := ctx.Int("port") // Checks command's --port, then global --port
- verbose := ctx.Bool("verbose") // Checks command's --verbose, then global --verbose
- tags := ctx.StringSlice("tag") // Checks command's --tag, then global --tag
-
- fmt.Printf("Config: %s, Port: %d, Verbose: %t, Tags: %v\n",
- configFile, port, verbose, tags)
-
- // Accessing Metadata
- appName := ctx.CLI.Name // Get the application name
- appVersion := ctx.CLI.Version // Get the application version
- commandName := ""
- if ctx.Command != nil { // Command is nil if running the global Action
- commandName = ctx.Command.Name // Get the name of the running command
- }
-
- fmt.Printf("Running command '%s' in app '%s' v%s\n",
- commandName, appName, appVersion)
-
- return nil
-}
-
---version flag (and -v alias). When used, it prints AppName version AppVersion and exits. You don’t need to define this flag yourself.help command.
-myapp help shows the main application help (description, usage, commands, global options).myapp help <command> shows detailed help for that specific command (description, usage, arguments, command-specific options, aliases).-h/--help): Nova reserves the names help and h for flags within a command’s definition (Command.Flags). You cannot define flags with these names. Users should use the help command (myapp help <command>) to get help for a specific command.nova.NewCLI(app): Returns an error if the CLI configuration is invalid (e.g., missing Name/Version, conflicting reserved names). Check this error before proceeding.cli.Run(args): Returns errors encountered during execution:
-ActionFunc.Always check the error returned by Run and handle it appropriately (e.g., log it, exit with a non-zero status).
// In main()
-cli, err := nova.NewCLI(app)
-if err != nil {
- log.Fatalf("Initialization error: %v", err) // Handle NewCLI error
-}
-
-if err := cli.Run(os.Args); err != nil {
- log.Fatalf("Runtime error: %v", err) // Handle Run error
-}
-
-This example demonstrates global flags, command flags, required flags, default values, arguments, and context usage.
-package main
-
-import (
- "fmt"
- "log"
- "os"
-
- "github.com/xlc-dev/nova/nova"
-)
-
-func main() {
- app := &nova.CLI{
- Name: "greeter-server",
- Version: "1.1.0",
- Description: "A versatile app to greet users or start a server.",
- Authors: "Nova Developer",
- GlobalFlags: []nova.Flag{
- &nova.BoolFlag{
- Name: "debug",
- Aliases: []string{"d"},
- Usage: "Enable debug output globally",
- },
- &nova.StringFlag{
- Name: "log-file",
- Usage: "Path to write logs",
- Default: "",
- },
- },
- Commands: []*nova.Command{
- {
- Name: "hello",
- Aliases: []string{"hi"},
- Usage: "Prints a customizable greeting",
- ArgsUsage: "<name>", // Expects one argument
- Action: helloAction,
- Flags: []nova.Flag{
- &nova.StringFlag{
- Name: "greeting",
- Aliases: []string{"g"},
- Usage: "Greeting phrase",
- Default: "Hello",
- },
- &nova.IntFlag{
- Name: "repeat",
- Aliases: []string{"r"},
- Usage: "Number of times to repeat the greeting",
- Default: 1,
- },
- },
- },
- {
- Name: "serve",
- Usage: "Starts an HTTP(S) server",
- Description: "Starts a simple web server on the specified host and port.",
- Action: serveAction,
- Flags: []nova.Flag{
- &nova.IntFlag{
- Name: "port",
- Aliases: []string{"p"},
- Usage: "Server port number",
- Required: true, // Port is mandatory for serve
- },
- &nova.StringFlag{
- Name: "host",
- Usage: "Server host address",
- Default: "127.0.0.1",
- },
- &nova.StringFlag{
- Name: "tls-cert",
- Usage: "Path to TLS certificate file (enables HTTPS)",
- },
- },
- },
- },
- }
-
- cli, err := nova.NewCLI(app)
- if err != nil {
- log.Fatalf("Failed to initialize CLI: %v", err)
- }
-
- if err := cli.Run(os.Args); err != nil {
- log.Fatalf("Application error: %v", err)
- }
-}
-
-// logMessage now takes nova.Context to access global flags
-func logMessage(ctx *nova.Context, format string, a ...any) {
- // Get global flag values from context
- debugMode := ctx.Bool("debug")
- logFileDest := ctx.String("log-file")
-
- msg := fmt.Sprintf(format, a...)
-
- // Log to file if specified
- if logFileDest != "" {
- f, err := os.OpenFile(
- logFileDest,
- os.O_APPEND|os.O_CREATE|os.O_WRONLY,
- 0644,
- )
- if err == nil {
- defer f.Close()
- if _, writeErr := f.WriteString(msg + "\n"); writeErr != nil {
- // Log the error of writing to the file to stderr
- fmt.Fprintf(
- os.Stderr,
- "Error writing to log file %s: %v\n",
- logFileDest,
- writeErr,
- )
- }
- } else {
- // Log the error of opening the file to stderr
- fmt.Fprintf(
- os.Stderr,
- "Error opening log file %s: %v\n",
- logFileDest,
- err,
- )
- }
- }
-
- // Conditional console output:
- // Print if debug mode is on, or if no log file is specified.
- if debugMode || logFileDest == "" {
- fmt.Println(msg)
- }
-}
-
-func helloAction(ctx *nova.Context) error {
- // Access global flag "debug" via context
- if ctx.Bool("debug") {
- logMessage(ctx, "Debug: Running 'hello' command")
- }
-
- args := ctx.Args()
- if len(args) == 0 {
- // ArgsUsage suggests <name>, ActionFunc should still validate
- return fmt.Errorf("missing required argument: <name>")
- }
- name := args[0]
-
- // Access command-specific flags via context
- greeting := ctx.String("greeting")
- repeat := ctx.Int("repeat")
-
- message := fmt.Sprintf("%s, %s!", greeting, name)
-
- for range repeat {
- logMessage(ctx, message)
- }
-
- if ctx.Bool("debug") {
- logMessage(ctx, "Debug: 'hello' command finished")
- }
- return nil
-}
-
-func serveAction(ctx *nova.Context) error {
- if ctx.Bool("debug") {
- logMessage(ctx, "Debug: Running 'serve' command")
- // Access global flag "log-file" via context for logging
- logMessage(ctx, "Debug: Global log file: %q", ctx.String("log-file"))
- }
-
- // Access command-specific flags via context
- host := ctx.String("host")
- port := ctx.Int("port") // Required flag, nova should ensure it's present
- tlsCert := ctx.String("tls-cert")
-
- protocol := "HTTP"
- if tlsCert != "" {
- protocol = "HTTPS"
- }
-
- logMessage(ctx, "Starting %s server on %s:%d...", protocol, host, port)
-
- if tlsCert != "" {
- logMessage(ctx, "Using TLS certificate: %s", tlsCert)
- fmt.Printf(
- "Simulating HTTPS server start on %s:%d with cert %s\n",
- host,
- port,
- tlsCert,
- )
- } else {
- // Add actual HTTP server start logic here
- fmt.Printf("Simulating HTTP server start on %s:%d\n", host, port)
- }
-
- logMessage(ctx, "Server running (simulation). Press Ctrl+C to stop.")
- // Block or wait for server goroutine in a real app
- select {} // Simulate running server
-}
-
-
- Nova provides a simple, file-based database migration tool accessible directly through the nova command-line binary. It allows you to manage database schema changes using plain SQL files stored in a dedicated folder, driven by simple commands.
nova migrate <action>.up) and reverting (down) the change.DATABASE_URL environment variable (supports .env files).postgres, mysql, sqlite) based on the DATABASE_URL prefix.DATABASE_URL)Set DATABASE_URL: Ensure the DATABASE_URL environment variable is set correctly for your target database. You can also place it in a .env file in the directory where you run nova.
# Example for PostgreSQL
-export DATABASE_URL="postgres://user:password@host:port/dbname?sslmode=disable"
-
-# Example for SQLite
-export DATABASE_URL="file:./my_app_data.db"
-
-Create the migrations Folder: In the root of your project (or where you run nova), create the folder:
mkdir migrations
-
-Create Your First Migration: Use the new command:
nova migrate new create_users_table
-# Output: Created new migration: migrations/1678886400_create_users_table.sql (timestamp will vary)
-
-Edit the SQL File: Open the generated .sql file and add your SQL statements under the appropriate delimiters:
-- migrations/1678886400_create_users_table.sql
-
--- migrate:up
-CREATE TABLE users (
- id SERIAL PRIMARY KEY,
- username VARCHAR(50) UNIQUE NOT NULL,
- created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
-);
-
--- migrate:down
-DROP TABLE IF EXISTS users;
-
-Apply the Migration: Use the up command:
nova migrate up
-# Output: Applied migration migrations/1678886400_create_users_table.sql
-# Output: Successfully applied 1 migration(s).
-
-Your database schema is now updated!
-DATABASE_URL)The nova migrate command requires the DATABASE_URL environment variable to connect to your database.
postgres://...)mysql://...)file:... or path ending in .db).env File: If a .env file exists in the current directory, nova migrate will attempt to load environment variables from it. Variables already present in the environment take precedence.These concepts explain how the migration files and tracking work.
-migrations Foldermigrations.nova migrate command or call the programmatic functions..sql migration files.<version>_<descriptive_name>.sql<version>: An integer, typically a Unix timestamp (e.g., 1678886400), used for ordering. Generated automatically by nova migrate new.<descriptive_name>: Briefly explains the migration’s purpose (e.g., create_users_table)..sql).-- migrate:up and -- migrate:down delimiters.-- migrate:up are executed by nova migrate up.-- migrate:down are executed by nova migrate down.-- migrations/TIMESTAMP_add_email_to_users.sql
-
--- migrate:up
-ALTER TABLE users ADD COLUMN email VARCHAR(255) UNIQUE;
-CREATE INDEX idx_users_email ON users(email);
-
--- migrate:down
-DROP INDEX IF EXISTS idx_users_email;
-ALTER TABLE users DROP COLUMN IF EXISTS email;
-
-schema_version table)nova migrate up or nova migrate down runs (either via CLI or programmatically), it automatically creates a table named schema_version in your database.<version> number of the most recently applied migration.Manage your database schema using these subcommands of nova migrate.
nova migrate newCreates a new migration file skeleton.
-nova migrate new <migration_name><migration_name> (Required): A descriptive name for the migration (e.g., add_indexes_to_orders). Use underscores for spaces.migrations folder if needed.<timestamp>_<migration_name>.sql.-- migrate:up and -- migrate:down template into the file.nova migrate new add_last_login_to_users
-# Creates migrations/1678886402_add_last_login_to_users.sql (example)
-
-nova migrate upApplies pending migrations to the database.
-Syntax: nova migrate up [steps]
Arguments:
-[steps] (Optional): An integer specifying the maximum number of migrations to apply. If omitted or 0, applies all pending migrations.Behavior:
-DATABASE_URL.schema_version table..sql files in the migrations folder with a version greater than the current version.-- migrate:up for each pending migration, up to the specified [steps] limit.schema_version table after each successful migration file application.Examples:
-# Apply all pending migrations
-nova migrate up
-
-# Apply only the next 2 pending migrations
-nova migrate up 2
-
-nova migrate downRolls back previously applied migrations.
-Syntax: nova migrate down [steps]
Arguments:
-[steps] (Optional): An integer specifying the exact number of migrations to roll back. If omitted or 0, defaults to rolling back one migration.Behavior:
-DATABASE_URL.schema_version table..sql files in the migrations folder with a version less than or equal to the current version.-- migrate:down for the most recent applied migrations, up to the specified number of [steps].schema_version table after each successful rollback to reflect the new latest version.Examples:
-# Roll back the single most recent migration
-nova migrate down
-# OR
-nova migrate down 1
-
-# Roll back the last 3 applied migrations
-nova migrate down 3
-
-While the nova migrate CLI command is the recommended way to manage migrations, the underlying functions are exported and can be used directly within your Go code if you need more control or want to embed migration logic into your application’s startup sequence or custom tooling.
Note: When using these functions programmatically, you are responsible for obtaining the *sql.DB database connection handle yourself. The functions do not read the DATABASE_URL environment variable out of the box.
CreateNewMigrationfunc CreateNewMigration(name string) error
-
-Identical in behavior to the CLI’s new action, but called from Go code.
name (string): Descriptive name for the migration.nil.Usage:
-import "github.com/xlc-dev/nova/nova"
-
-// ... inside your Go code ...
-err := nova.CreateNewMigration("seed_initial_data")
-if err != nil {
- log.Printf("Failed to create migration file: %v", err)
- // Handle error appropriately
-}
-
-MigrateUpfunc MigrateUp(db *sql.DB, steps int) error
-
-Identical in behavior to the CLI’s up action, but called from Go code.
db (*sql.DB): An active database connection handle obtained via sql.Open.steps (int): Max number of migrations to apply (0 = all).nil.Usage:
-import (
- "database/sql"
-
- _ "github.com/lib/pq" // Import your DB driver
- "github.com/xlc-dev/nova/nova"
-)
-
-// ... inside your Go code ...
-db, err := sql.Open("postgres", "your_connection_string")
-if err != nil { /* handle error */ }
-defer db.Close()
-
-log.Println("Applying database migrations...")
-// Apply all pending migrations during application startup
-err = nova.MigrateUp(db, 0)
-if err != nil {
- log.Fatalf("Database migration failed: %v", err) // Critical error on startup
-}
-log.Println("Database migrations applied successfully.")
-
-MigrateDownfunc MigrateDown(db *sql.DB, steps int) error
-
-Identical in behavior to the CLI’s down action, but called from Go code.
db (*sql.DB): An active database connection handle.steps (int): Number of migrations to roll back (0 or negative = 1).nil.Usage:
-import (
- "database/sql"
-
- _ "github.com/lib/pq" // Import your DB driver
- "github.com/xlc-dev/nova/nova"
-)
-
-// ... inside custom tooling or specific Go code ...
-db, err := sql.Open("postgres", "your_connection_string")
-if err != nil { /* handle error */ }
-defer db.Close()
-
-log.Println("Rolling back the last migration...")
-// Roll back one migration
-err = nova.MigrateDown(db, 1)
-if err != nil {
- log.Printf("Rollback failed: %v", err)
- // Handle error appropriately
-} else {
- log.Println("Rollback successful.")
-}
-
-Important: When using these functions programmatically, the status messages (Applied migration..., Rolled back migration...) are still printed to standard output. You might want to capture/redirect stdout if integrating into a larger system where this output is undesirable.
This document describes the design of the Nova framework and its architecture.
-NewRouter)Router.Handle)Router.Group & Group.Handle)Router.Use)Router.Subrouter)Router.ServeHTTP)Router.URLParam)Nova is designed to be lightweight and flexible. It’s built on top of the standard library,
-without any external dependencies except fsnotify
-for file watching and database drivers for migrations and database/sql support.
The supported database drivers are:
- -it is very easy to add a new databse driver -to nova if requested, as long as it follows the design philosophy of the framework.
-The goal of the framework is to be modular and extensible, so it is designed to follow as many -standards as possible wihtin the Go community, with the goal being able to plug and play different components within the framework.
-In this chapter you will find diagrams of the architecture of the Nova framework.
-One of the core components of the Nova framework is the CLI. -The CLI is able to parse any command line arguments and flags, and execute actions based on the provided flags.
-The overall execution flow can be broken down into several key stages:
-This initial stage of CLI.Run handles the setup and processing of global aspects of the command.
-It verifies that the CLI has been properly initialized. Then, it separates the provided arguments into global flags and the remaining arguments (which might contain a command and its specific flags).
-Global flags are parsed and validated. A special check for a --version flag is performed to allow for quick version printing without further processing.
-If global flag parsing or validation fails, the process terminates with an error. Otherwise, it proceeds to find and execute a command.
flowchart TB
- A["CLI.Run(args)"] --> B{"CLI Initialized?"}
- B -- No --> B_ERR["ERR: CLI not initialized"]
- B_ERR --> Z_END_INIT_ERR["End (Error)"]
- B -- Yes --> C["Split args: global flags & rest"]
- C --> D["Parse Global Flags (uses parseFlagSet helper)"]
- D --> D_CTX["Create initial Context (w/ globalSet)"]
- D_CTX --> E{"Global Parse Err?"}
- E -- Yes --> E_ERR["ERR: Global parse failed"]
- E_ERR --> Z_END_GLOBAL_PARSE_ERR["End (Error)"]
- E -- No --> F{"--version flag?"}
- F -- Yes --> G["Print Version"]
- G --> Z_SUCCESS_VERSION["End (Success)"]
- F -- No --> H["Validate Global Flags (uses validateFlags helper)"]
- H --> I{"Global Validate Err?"}
- I -- Yes --> I_ERR["ERR: Global validate failed"]
- I_ERR --> Z_END_GLOBAL_VALIDATE_ERR["End (Error)"]
- I -- No --> J_LINK["Proceed to: Command Search & Execution"]
-
- subgraph subGraph0_ref["Ref: parseFlagSet Helper"]
- direction TB
- REF_PF["(See 'Helper: parseFlagSet' diagram)"]
- end
-
- subgraph subGraph1_ref["Ref: validateFlags Helper"]
- direction TB
- REF_VF["(See 'Helper: validateFlags' diagram)"]
- end
-
- D -.-> REF_PF
- H -.-> REF_VF
-
-parseFlagSetThis helper function is responsible for the mechanics of parsing a set of flags.
-It takes a list of flag definitions, the arguments to parse, a name for the flag set (often “global” or the command name), and an output stream (for help messages).
-It creates a standard flag.FlagSet instance, applies the defined flags to this set, and then calls the Parse method on the set with the provided arguments.
-It returns the populated FlagSet and any error encountered during parsing.
flowchart TB - subgraph subGraph0["Helper: parseFlagSet"] - direction TB - PF1["In: flags, args, name, out"] - PF2["Create flag.FlagSet"] - PF3["Apply Flags to Set"] - PF4["FlagSet.Parse(args)"] - PF5["Return FlagSet, err"] - end - PF1 --> PF2 - PF2 --> PF3 - PF3 --> PF4 - PF4 --> PF5 --
validateFlagsAfter flags are parsed by parseFlagSet, this helper function is used to perform custom validation on each flag.
-It iterates through a list of flag definitions and, for each flag, calls its Validate method, passing in the FlagSet (which contains the parsed values).
-If any flag’s Validate method returns an error, these errors are combined and returned. If all flags validate successfully, it returns nil.
flowchart TB
- subgraph subGraph1["Helper: validateFlags"]
- direction TB
- VF1["In: flags, flagSet"]
- VF2["For each Flag"]
- VF3["Flag.Validate(flagSet)"]
- VF4{"Any Errors?"}
- VF5["Return combined err"]
- VF6["Return nil"]
- end
- VF1 --> VF2
- VF2 --> VF3
- VF3 --> VF4
- VF4 -- Yes --> VF5
- VF4 -- No --> VF6
-
-Once global flags are successfully parsed and validated, the CLI attempts to identify and execute a specific command.
-It searches for a command based on the first argument in restArgs (the arguments remaining after global flags). This search includes registered commands, their aliases, and a built-in help command.
If a command is found:
-help command, the context is prepared for help output, and the help action is executed.parseFlagSet) and validated (using validateFlags). The CLI context is updated with command-specific information. If parsing or validation fails, an error is reported. Otherwise, the command’s action is executed.If no command is found matching the first argument, the flow proceeds to the fallback mechanisms.
-flowchart TB
- J_START["From: Global Flag Validation Success"] --> J_NODE
- J_NODE["Find Command (in restArgs[0]). iterates cmds, aliases, 'help'"] --> K{"Cmd Found?"}
- K -- Yes --> L{"Cmd is 'help'?"}
- L -- Yes --> M["Ctx args for 'help', Exec 'help' Action"]
- M --> Z_SUCCESS_HELP["End (Success)"]
- L -- No (User Cmd) --> N["Parse Cmd Flags (uses parseFlagSet helper)"]
- N --> N_CTX["Update Context (w/ Cmd, cmdSet)"]
- N_CTX --> O{"Cmd Parse Err?"}
- O -- Yes --> O_ERR["ERR: Cmd parse failed"]
- O_ERR --> Z_END_CMD_PARSE_ERR["End (Error)"]
- O -- No --> P["Validate Cmd Flags (uses validateFlags helper)"]
- P --> Q{"Cmd Validate Err?"}
- Q -- Yes --> Q_ERR["ERR: Cmd validate failed"]
- Q_ERR --> Z_END_CMD_VALIDATE_ERR["End (Error)"]
- Q -- No --> R["Set ctx args, Exec Cmd Action"]
- R --> Z_SUCCESS_CMD_EXEC["End (Success)"]
-
- K -- No --> S_LINK["Proceed to: Fallback Flow (Global Action / Unknown Cmd)"]
-
- subgraph subGraph0_ref_cmd["Ref: parseFlagSet Helper"]
- direction TB
- REF_PF_CMD["(See 'Helper: parseFlagSet' diagram)"]
- end
-
- subgraph subGraph1_ref_cmd["Ref: validateFlags Helper"]
- direction TB
- REF_VF_CMD["(See 'Helper: validateFlags' diagram)"]
- end
-
- N -.-> REF_PF_CMD
- P -.-> REF_VF_CMD
-
-This flow is triggered if no specific command is identified from the arguments. -The CLI checks if a global action (an action to be performed if no command is specified) has been defined.
-restArgs) are set in the context, and the global action is executed.restArgs.
-restArgs exist, it implies the user tried to run a command that doesn’t exist, so an “Unknown command” error is shown.restArgs exist (meaning only global flags or no arguments were provided, and no command was matched), the main help message for the CLI application is displayed.flowchart TB
- S_START["From: Command Not Found"] --> S{"Global Action Defined?"}
- S -- Yes --> T["Set ctx args (restArgs), Exec Global Action"]
- T --> Z_SUCCESS_GLOBAL_ACTION["End (Success)"]
- S -- No --> U{"restArgs exist (unknown cmd)?"}
- U -- Yes --> V["ERR: Unknown command"]
- V --> Z_END_UNKNOWN_CMD_ERR["End (Error)"]
- U -- No --> W["Show Main Help"]
- W --> Z_SUCCESS_MAIN_HELP["End (Success)"]
-
-The nova Router is responsible for directing incoming HTTP requests to the appropriate handler functions based on the request’s method and URL path.
-It supports dynamic path parameters with optional regex validation, middleware, sub-routers for modular organization, and route groups for applying common prefixes and middleware to a set of routes.
Users primarily interact with the Router by creating a new instance, -registering routes with associated handlers and middleware, potentially mounting sub-routers or creating route groups.
-flowchart TD
- A_USER_ACTION{"User Configures & Uses Router"}
- A_USER_ACTION --> A1["Calls NewRouter()"]
- A_USER_ACTION --> A2["Calls router.Handle() (or Get(), Post(), etc.)"]
- A_USER_ACTION --> A3["Calls router.Use(middleware)"]
- A_USER_ACTION --> A4["Calls router.Subrouter(prefix)"]
- A_USER_ACTION --> A5["Calls router.Group(prefix)"]
- A_USER_ACTION --> A6["Router used as http.Handler"]
-
- A1 --> REF_NEW_ROUTER["(Details in 'Flow: Router Initialization')"]
- A2 --> REF_HANDLE["(Details in 'Flow: Route Registration')"]
- A3 --> REF_USE_MW["(Details in 'Flow: Middleware Registration')"]
- A4 --> REF_SUBROUTER["(Details in 'Flow: Subrouter Creation')"]
- A5 --> REF_GROUP["(Details in 'Flow: Group Creation & Handling')"]
- A6 --> REF_SERVE_HTTP["(Details in 'Flow: Request Dispatching - ServeHTTP')"]
-
- REF_NEW_ROUTER --> Z_END_CONFIG["Router Configured"]
- REF_HANDLE --> Z_END_CONFIG
- REF_USE_MW --> Z_END_CONFIG
- REF_SUBROUTER --> Z_END_CONFIG
- REF_GROUP --> Z_END_CONFIG
- REF_SERVE_HTTP --> Z_END_REQUEST_HANDLED["Request Handled / Error"]
-
-NewRouter)This function creates a new, empty Router instance. It initializes internal slices for routes and sub-routers, sets up a unique context key for URL parameters, and establishes a default (passthrough) middleware chain.
flowchart TD - NR_START["Start NewRouter()"] --> NR1["Initialize Router struct: empty routes, subrouters, middlewares"] - NR1 --> NR2["Set unique paramsKey for context"] - NR2 --> NR3["Set basePath to empty"] - NR3 --> NR4["Initialize middleware chain (default: passthrough)"] - NR4 --> NR_OUTPUT["Output: New Router Instance"] - NR_OUTPUT --> NR_END["End NewRouter"] --
Router.Handle)This is the core method for defining a route. It takes an HTTP method, a URL pattern, and a handler function. The URL pattern is compiled into segments (literals or parameters with optional regex). If the router has a basePath (e.g., if it’s a subrouter), this path is prepended to the given pattern. The compiled route is then stored. Helper methods like Get(), Post() internally call Handle().
flowchart TD - RH_START["Start router.Handle(method, pattern, handler, opts...)"] --> RH1["Check if router has basePath"] - RH1 -- Yes --> RH2_JOIN["Prepend router.basePath to pattern (uses joinPaths)"] - RH1 -- No --> RH2_COMPILE - RH2_JOIN --> RH2_COMPILE["Compile fullPattern into segments (uses compilePattern)"] - RH2_COMPILE -- Error (Invalid Pattern) --> RH_PANIC["Panic: Invalid Route Pattern"] - RH2_COMPILE -- Success (segments) --> RH3["Create route struct (method, handler, segments, options)"] - RH3 --> RH4["Append new route to router.routes slice"] - RH4 --> RH_END["End router.Handle"] --
Internal Detail: compilePattern(pattern)
-This helper parses a URL pattern string (e.g., /users/{id:\d+}) into a sequence of segment structs. Each segment is either a literal string or a parameter (e.g., id). Parameters can include an optional regex (e.g., \d+) which is pre-compiled for efficient matching.
Router.Group & Group.Handle)A Group allows defining a common prefix and/or middleware for a set of routes. Router.Group() creates a Group instance, storing the prefix and any group-specific middleware. When Group.Handle() (or group.Get(), etc.) is called, it prepends the group’s prefix to the route pattern, wraps the handler with the group’s middleware, and then calls the underlying router.Handle() method.
flowchart TD - %% Router.Group - RG_START_ROUTER["Start router.Group(prefix, mws...)"] --> RG1["Join router.basePath with group prefix"] - RG1 --> RG2["Create Group struct (prefix, router instance, group middlewares)"] - RG2 --> RG_OUTPUT["Output: New Group Instance"] - RG_OUTPUT --> RG_END_ROUTER["End router.Group"] - - %% Group.Handle - GH_START["Start group.Handle(method, pattern, handler, opts...)"] --> GH1["Prepend group.prefix to pattern (uses joinPaths)"] - GH1 --> GH2_WRAP["Wrap provided handler with group's middlewares"] - GH2_WRAP --> GH3_FORWARD["Call underlying router.Handle(method, fullPattern, wrappedHandler, opts...)"] - GH3_FORWARD --> REF_ROUTER_HANDLE["(Uses 'Flow: Route Registration')"] - REF_ROUTER_HANDLE --> GH_END["End group.Handle"] --
Router.Use)This method allows adding one or more Middleware functions to the router. These middlewares are applied globally to all routes handled by this router (and inherited by its subrouters). After adding new middleware, the router’s internal middleware chain is rebuilt.
flowchart TD - MWU_START["Start router.Use(mws...)"] --> MWU1["Append new middleware(s) to router.middlewares slice"] - MWU1 --> MWU2["Rebuild router's middleware chain (rebuildChain)"] - MWU2 --> MWU_END["End router.Use"] --
Internal Detail: rebuildChain()
-This function iterates through the registered middlewares in reverse order, wrapping the final handler (or the previously wrapped handler) with each middleware. This creates a single composed http.Handler that executes all middlewares in the correct sequence before reaching the route-specific handler.
Router.Subrouter)Subrouter allows mounting another Router instance at a specified path prefix. The new subrouter inherits the parent’s context key for parameters, its global middleware chain, and custom error handlers. The basePath of the subrouter is set by joining the parent’s basePath with the new prefix.
flowchart TD - SR_START["Start router.Subrouter(prefix)"] --> SR1["Initialize new Router instance"] - SR1 --> SR2["Inherit paramsKey from parent"] - SR2 --> SR3["Copy parent's middlewares & rebuild chain for subrouter"] - SR3 --> SR4["Set subrouter.basePath (join parent.basePath + prefix)"] - SR4 --> SR5["Inherit notFoundHandler & methodNotAllowedHandler"] - SR5 --> SR6["Append new subrouter to parent's subrouters slice"] - SR6 --> SR_OUTPUT["Output: New Subrouter Instance"] - SR_OUTPUT --> SR_END["End router.Subrouter"] --
Router.ServeHTTP)This is the core of the router, implementing http.Handler. When a request arrives:
basePath of any registered subrouters. If so, the request is delegated to that subrouter’s ServeHTTP method.matchSegments).flowchart TD
- SH_START["Start router.ServeHTTP(w, req)"] --> SH1_SUBROUTERS{"Check Subrouters: Path matches subrouter.basePath?"}
- SH1_SUBROUTERS -- Yes, matches SR --> SH2_DELEGATE_SR["Delegate to sr.ServeHTTP(w, req) & return"]
- SH1_SUBROUTERS -- No subrouter match --> SH3_LOOP_ROUTES{"Loop own routes: Attempt to match req.URL.Path (matchSegments)"}
-
- SH3_LOOP_ROUTES -- No match in loop --> SH_HANDLE_404["Pattern Not Matched: Trigger 404 Not Found (custom or default)"]
- SH_HANDLE_404 --> SH_END_REQUEST["End Request"]
-
- SH3_LOOP_ROUTES -- Path Pattern Matched (ok, params) --> SH4_CHECK_METHOD{"Method Matches rt.method?"}
- SH4_CHECK_METHOD -- Yes (Full Match) --> SH5_ADD_PARAMS["Add URL params to req.Context (if any)"]
- SH5_ADD_PARAMS --> SH6_APPLY_CHAIN["Apply router's middleware chain to rt.handler"]
- SH6_APPLY_CHAIN --> SH7_EXEC_HANDLER["Execute finalHandler.ServeHTTP(w, req) & return"]
-
- SH4_CHECK_METHOD -- No (Path matched, method didn't) --> SH_HANDLE_405["Pattern Matched, Method Mismatch: Trigger 405 Method Not Allowed (custom or default)"]
- SH_HANDLE_405 --> SH_END_REQUEST
-
- SH2_DELEGATE_SR --> SH_END_REQUEST
- SH7_EXEC_HANDLER --> SH_END_REQUEST
-
-Internal Detail: matchSegments(path, segments)
-This helper compares the parts of an incoming URL path against a route’s pre-compiled segments. It checks literal matches and, for parameter segments, validates against any compiled regex and extracts the parameter value. It returns true and a map of parameters if matched.
Router.URLParam)A simple utility to retrieve a named URL parameter that was extracted during routing. It accesses the parameters map stored in the request’s context using the router’s unique paramsKey.
flowchart TD
- UP_START["Start router.URLParam(req, key)"] --> UP1["Get params map from req.Context using router.paramsKey"]
- UP1 --> UP2{"Params map found & key exists?"}
- UP2 -- Yes --> UP3_RETURN_VALUE["Output: Parameter value (string)"]
- UP2 -- No --> UP4_RETURN_EMPTY["Output: Empty string"]
- UP3_RETURN_VALUE --> UP_END["End URLParam"]
- UP4_RETURN_EMPTY --> UP_END
-
-Scaffolding provides functionality to generate new Go project structures from predefined, embedded templates. -It customizes directory names, filenames, and file contents based on user input (like project name and database choice), -effectively bootstrapping a new application with a chosen layout.
-The core logic resides in the createFromTemplate function, w -hich is invoked by higher-level functions like CreateMinimal, or CreateStructured. -It systematically processes template items to build the new project. If any step fails, the scaffolding process is halted, and an error is returned.
-flowchart TD
- A_USER_CALL["User calls CreateMinimal or CreateStructured"] --> B_INVOKE_CFT["Invoke createFromTemplate(name, templateConfig, dbImport)"]
-
- B_INVOKE_CFT --> C_START_CFT["Start createFromTemplate"]
-
- C_START_CFT --> D_MKDIR_ROOT["Create Project Root Directory (os.Mkdir)"]
- D_MKDIR_ROOT -- Error --> Z_ERROR["Scaffolding Failed"]
- D_MKDIR_ROOT -- Success --> E_PREPARE_DATA["Prepare Template Data (name, dbImport, getDBAdapter() -> templateData)"]
-
- E_PREPARE_DATA --> F_WALK_FS_SETUP["Setup fs.WalkDir to iterate over embedded templates"]
- F_WALK_FS_SETUP -- Walk Setup Error --> Z_ERROR
- F_WALK_FS_SETUP -- Start Iteration --> G_WALK_CALLBACK{"fs.WalkDir Callback for each entry (originalPath, dirEntry, err)"}
-
- G_WALK_CALLBACK -- Error from WalkDir itself --> Z_ERROR
- G_WALK_CALLBACK -- Process Entry --> H_PROCESS_PATH["Process Path Template - Get relative path - Strip .tmpl - Execute path as template - Result: targetPath"]
- H_PROCESS_PATH -- Error --> Z_ERROR_CALLBACK["Return error from callback"]
- H_PROCESS_PATH -- Success (targetPath) --> I_IS_DIR{"Is Entry a Directory?"}
-
- I_IS_DIR -- Yes --> J_HANDLE_DIR["Create Directory (handleDirectory) (os.MkdirAll at targetPath, log if verbose)"]
- J_HANDLE_DIR -- Error --> Z_ERROR_CALLBACK
- J_HANDLE_DIR -- Success --> G_WALK_CALLBACK_NEXT["Return nil (continue walk)"]
-
-
- I_IS_DIR -- No (File) --> K_HANDLE_FILE_START["Process & Write File (handleFile)"]
- K_HANDLE_FILE_START --> L_READ_TEMPLATE["Read Template File Content (fs.ReadFile from originalPath)"]
- L_READ_TEMPLATE -- Error --> Z_ERROR_CALLBACK
- L_READ_TEMPLATE -- Success (rawContent) --> M_PROCESS_CONTENT["Process Content Template (processContentTemplate) (Execute rawContent with templateData)"]
- M_PROCESS_CONTENT -- Error --> Z_ERROR_CALLBACK
- M_PROCESS_CONTENT -- Success (processedContent) --> N_WRITE_FILE["Write Processed Content to disk (os.WriteFile at targetPath, log if verbose)"]
- N_WRITE_FILE -- Error --> Z_ERROR_CALLBACK
- N_WRITE_FILE -- Success --> G_WALK_CALLBACK_NEXT
-
- Z_ERROR_CALLBACK --> G_WALK_TERMINATES_ERROR["fs.WalkDir terminates with error"]
- G_WALK_CALLBACK_NEXT --> G_WALK_CALLBACK_CONTINUE["fs.WalkDir continues to next entry or finishes"]
-
- G_WALK_CALLBACK_CONTINUE -- More Entries --> G_WALK_CALLBACK
- G_WALK_CALLBACK_CONTINUE -- All Entries Processed (WalkDir returns nil) --> Y_SUCCESS["createFromTemplate Successful"]
-
- G_WALK_TERMINATES_ERROR --> Z_ERROR
-
- Y_SUCCESS --> Z_SUCCESS["Scaffolding Succeeded"]
-
- Z_SUCCESS --> Z_END["End"]
- Z_ERROR --> Z_END
-
-Nova provides database migrations through versioned SQL files. This allows for creating new migrations, applying pending changes (migrating up), and reverting applied changes (migrating down).
-Users interact with the migration system by calling one of three main functions:
-CreateNewMigration to scaffold a new migration file, MigrateUp to apply pending schema changes to the database, or MigrateDown to roll back previously applied changes from the database.
-Each of these actions follows a distinct flow.
flowchart TD
- A_USER_ACTION{"User Initiates Migration Task"}
- A_USER_ACTION --> A1["Calls CreateNewMigration(name)"]
- A_USER_ACTION --> A2["Calls MigrateUp(db, steps)"]
- A_USER_ACTION --> A3["Calls MigrateDown(db, steps)"]
-
- A1 --> REF_CREATE["(Details in 'Flow: CreateNewMigration')"]
- A2 --> REF_UP["(Details in 'Flow: MigrateUp')"]
- A3 --> REF_DOWN["(Details in 'Flow: MigrateDown')"]
-
- REF_CREATE --> Z_END_ACTION["End User Action"]
- REF_UP --> Z_END_ACTION
- REF_DOWN --> Z_END_ACTION
-
-CreateNewMigrationThis function is responsible for scaffolding a new SQL migration file.
-It generates a unique, timestamped filename, ensures the migrations directory exists (creating it if necessary),
-and then writes a basic template into the new file.
-This template includes the "-- migrate:up" and "-- migrate:down" delimiters to guide the user in adding their SQL statements.
flowchart TD - CNM_START["Start CreateNewMigration(name)"] --> CNM1["Generate Timestamped Filename (e.g., 123_name.sql)"] - CNM1 --> CNM2["Ensure 'migrations' Folder Exists (Create if not)"] - CNM2 --> CNM3["Write SQL Template (up/down sections) to New File"] - CNM3 -- Success --> CNM_SUCCESS["Output: New .sql File Created"] - CNM3 -- Error --> CNM_ERROR["Output: Error During File Creation"] - CNM_SUCCESS --> CNM_END["End CreateNewMigration"] - CNM_ERROR --> CNM_END --
MigrateUpThe MigrateUp function applies pending schema changes to the database.
-It first determines the current version of the database.
-Then, it identifies all migration files in the migrations folder that have a version number greater than the current database version.
-These pending migrations are sorted chronologically (oldest first) and applied sequentially, up to an optional steps limit.
-For each migration applied, its “up” SQL statements are executed, and the database version is updated.
flowchart TD
- MU_START["Start MigrateUp(db, steps)"] --> MU1["Get Current DB Version"]
- MU1 -- Error --> MU_END_ERROR_INIT["End (Error Initializing)"]
- MU1 -- Success (currentDBVer) --> MU2["Get & Sort Migration Files (Oldest First)"]
- MU2 -- Error --> MU_END_ERROR_INIT
- MU2 -- Success (sortedFiles) --> MU3_LOOP_START{"Loop: For each file (respect 'steps' limit)"}
-
- MU3_LOOP_START -- No More Applicable Files / Limit Reached --> MU_REPORT["Report Status (Applied count or No pending)"]
- MU_REPORT --> MU_END_SUCCESS["End (MigrateUp Finished)"]
-
- MU3_LOOP_START -- Next File --> MU4_CHECK_VER{"Check: File Version > currentDBVer?"}
- MU4_CHECK_VER -- No (Skip) --> MU3_LOOP_START
- MU4_CHECK_VER -- Yes (Pending) --> MU5_APPLY["Action: Read File & Apply 'Up' SQL Statements"]
- MU5_APPLY -- Error --> MU_END_ERROR_APPLY["End (Error Applying SQL)"]
- MU5_APPLY -- Success --> MU6_UPDATE_DB["Action: Update DB Version to This File's Version"]
- MU6_UPDATE_DB -- Error --> MU_END_ERROR_DBUPDATE["End (Error Updating DB Version)"]
- MU6_UPDATE_DB -- Success --> MU7_LOG["Action: Log Applied & Increment Count"]
- MU7_LOG --> MU3_LOOP_START
-
-MigrateDownThe MigrateDown function reverts previously applied migrations.
-It starts by getting the current database version.
-It then considers migration files sorted in reverse chronological order (newest first).
-For each migration whose version is less than or equal to the current database version (and within the steps limit, which defaults to 1),
-its “down” SQL statements are executed. After a successful rollback, the database version is updated to reflect the state before that migration was applied.
flowchart TD
- MD_START["Start MigrateDown(db, steps)"] --> MD0["1. Set 'steps' (default 1 if 0 or less)"]
- MD0 --> MD1["2. Get Current DB Version"]
- MD1 -- Error --> MD_END_ERROR_INIT["End (Error Initializing)"]
- MD1 -- Success (currentDBVer) --> MD2["3. Get & Sort Migration Files (Newest First)"]
- MD2 -- Error --> MD_END_ERROR_INIT
- MD2 -- Success (sortedFiles) --> MD3_LOOP_START{"4. Loop: For each file (respect 'steps' limit)"}
-
- MD3_LOOP_START -- No More Applicable Files / Limit Reached --> MD_REPORT["7. Report Status (Rolled back count or None)"]
- MD_REPORT --> MD_END_SUCCESS["End (MigrateDown Finished)"]
-
- MD3_LOOP_START -- Next File --> MD4_CHECK_VER{"5a. File Version <= currentDBVer AND currentDBVer > 0?"}
- MD4_CHECK_VER -- No (Skip) --> MD3_LOOP_START
- MD4_CHECK_VER -- Yes (Can Rollback) --> MD5_APPLY["5b. Read File & Apply 'Down' SQL Statements"]
- MD5_APPLY -- Error --> MD_END_ERROR_APPLY["End (Error Applying SQL)"]
- MD5_APPLY -- Success --> MD6_UPDATE_DB["5c. Update DB Version (to version before this file)"]
- MD6_UPDATE_DB -- Error --> MD_END_ERROR_DBUPDATE["End (Error Updating DB Version)"]
- MD6_UPDATE_DB -- Success --> MD7_LOG["5d. Log Rolled Back & Increment Count"]
- MD7_LOG --> MD3_LOOP_START
-
-The nova framework can automatically generate an OpenAPI 3.0 specification from your router definitions and associated Go types.
-This allows for easy documentation and client generation. The process involves collecting route information, building operation details,
-and generating JSON schemas for request/response bodies and parameters.
-The generated specification can then be served as a JSON file, and an embedded Swagger UI can be hosted to visualize and interact with the API.
The generation of the OpenAPI specification is primarily orchestrated by GenerateOpenAPISpec.
-This function initializes the spec and then recursively traverses the router structure using collectRoutes to populate path and operation details.
-Once generated, ServeOpenAPISpec can expose it via an HTTP endpoint, and ServeSwaggerUI can provide an interactive API console.
flowchart TD
- A_USER_CALLS["User calls Router.ServeOpenAPISpec() or Router.ServeSwaggerUI()"]
-
- A_USER_CALLS --> B_SERVE_SPEC_OR_UI{"Serve Spec or UI?"}
-
- B_SERVE_SPEC_OR_UI -- ServeOpenAPISpec --> C1_GEN_SPEC["Invoke GenerateOpenAPISpec(router, config)"]
- C1_GEN_SPEC --> REF_GEN_SPEC["(Details in 'Flow: GenerateOpenAPISpec')"]
- REF_GEN_SPEC --> C2_MARSHAL["Marshal Spec to JSON"]
- C2_MARSHAL --> C3_SERVE_JSON["Register Handler to Serve JSON at specified path"]
- C3_SERVE_JSON --> Z_END_ACTION["End User Action"]
-
- B_SERVE_SPEC_OR_UI -- ServeSwaggerUI --> D1_SERVE_UI["Invoke ServeSwaggerUI(prefix)"]
- D1_SERVE_UI --> REF_SERVE_UI["(Details in 'Flow: ServeSwaggerUI')"]
- REF_SERVE_UI --> Z_END_ACTION
-
-GenerateOpenAPISpecThis is the main function responsible for constructing the complete OpenAPI specification object.
-It initializes the basic structure of the spec (like OpenAPI version, info)
-and then kicks off the route collection process.
-It uses a schemaGenCtx to manage and reuse generated schemas in the components section.
flowchart TD - GEN_START["Start GenerateOpenAPISpec(router, config)"] --> GEN1["Initialize OpenAPI Spec (Version, Info, Servers, empty Paths, empty Components)"] - GEN1 --> GEN2["Initialize schemaGenCtx (for managing component schemas)"] - GEN2 --> GEN3_COLLECT["Call collectRoutes"] - GEN3_COLLECT --> REF_COLLECT_ROUTES["(Details in 'Flow: collectRoutes')"] - REF_COLLECT_ROUTES --> GEN4["Populate spec.Components.Schemas from schemaCtx (if any)"] - GEN4 --> GEN_OUTPUT["Output: Populated OpenAPI Spec Object"] - GEN_OUTPUT --> GEN_END["End GenerateOpenAPISpec"] --
collectRoutes (Recursive)This function recursively traverses the router and its sub-routers.
-For each route it encounters, it constructs the full path string,
-creates or retrieves the corresponding PathItem in the OpenAPI spec,
-and then builds the Operation object for that specific HTTP method and path.
flowchart TD
- CR_START["Start collectRoutes(router, spec, schemaCtx, parentPath)"]
- CR_START --> CR1_LOOP_ROUTES{"For each route in router.routes"}
- CR1_LOOP_ROUTES -- Next Route --> CR2_BUILD_PATH["Build Full Path String (uses buildPathString)"]
- CR2_BUILD_PATH --> CR3_GET_PATHITEM["Get/Create PathItem in spec.Paths"]
- CR3_GET_PATHITEM --> CR4_BUILD_OP["Build Operation (uses buildOperation)"]
- CR4_BUILD_OP --> REF_BUILD_OP["(Details in 'Flow: buildOperation')"]
- REF_BUILD_OP --> CR5_ASSIGN_OP["Assign Operation to PathItem (e.g., pathItem.Get = op)"]
- CR5_ASSIGN_OP --> CR1_LOOP_ROUTES
- CR1_LOOP_ROUTES -- All Routes Processed --> CR6_LOOP_SUBROUTERS{"For each subrouter in router.subrouters"}
-
- CR6_LOOP_SUBROUTERS -- Next Subrouter --> CR7_RECURSE["Recursive Call: collectRoutes(subrouter, spec, schemaCtx, subrouter.basePath)"]
- CR7_RECURSE --> CR6_LOOP_SUBROUTERS
- CR6_LOOP_SUBROUTERS -- All Subrouters Processed --> CR_END["End collectRoutes"]
-
-buildOperationFor a given route, this function constructs an OpenAPI Operation object.
-It populates details like tags, summary, description, and parameters based on route.options.
-It also handles the generation of schemas for request bodies and responses by calling generateSchema.
-Path parameters defined in the route segments are also ensured to be part of the operation’s parameters.
flowchart TD
- BO_START["Start buildOperation(route, schemaCtx)"] --> BO1["Initialize Operation Object (empty Responses, Parameters)"]
- BO1 --> BO2_CHECK_OPTS{"Route Options Defined?"}
- BO2_CHECK_OPTS -- No --> BO_PROCESS_PATH_PARAMS
- BO2_CHECK_OPTS -- Yes (opts exist) --> BO3_POPULATE_META["Populate Meta (Tags, Summary, Desc, OpID, Deprecated)"]
- BO3_POPULATE_META --> BO4_REQ_BODY{"RequestBody Option?"}
- BO4_REQ_BODY -- Yes --> BO5_BUILD_REQ_BODY["Create RequestBodyObject, call generateSchema for its content"]
- BO5_BUILD_REQ_BODY --> REF_GEN_SCHEMA_REQ["(Uses 'Flow: generateSchema')"]
- REF_GEN_SCHEMA_REQ --> BO6_RESPONSES
- BO4_REQ_BODY -- No --> BO6_RESPONSES
-
- BO6_RESPONSES{"Loop Response Options"}
- BO6_RESPONSES -- Next ResponseOpt --> BO7_BUILD_RESP["Create ResponseObject, call generateSchema for body"]
- BO7_BUILD_RESP --> REF_GEN_SCHEMA_RESP["(Uses 'Flow: generateSchema')"]
- REF_GEN_SCHEMA_RESP --> BO6_RESPONSES
- BO6_RESPONSES -- Done --> BO8_PARAMS{"Loop Parameter Options"}
-
- BO8_PARAMS -- Next ParamOpt --> BO9_BUILD_PARAM["Create ParameterObject, call generateSchema for schema"]
- BO9_BUILD_PARAM --> REF_GEN_SCHEMA_PARAM["(Uses 'Flow: generateSchema')"]
- REF_GEN_SCHEMA_PARAM --> BO8_PARAMS
- BO8_PARAMS -- Done --> BO_PROCESS_PATH_PARAMS
-
- BO_PROCESS_PATH_PARAMS["Ensure Path Parameters from route.segments are added (if not already from options)"]
- BO_PROCESS_PATH_PARAMS --> BO10_DEFAULT_RESP{"Add Default '200 OK' Response if none specified"}
- BO10_DEFAULT_RESP --> BO_OUTPUT["Output: Populated Operation Object"]
- BO_OUTPUT --> BO_END["End buildOperation"]
-
-generateSchema (Recursive & Type Handling)This is the core schema generation logic.
-Given a Go interface{} instance,
-it uses reflection to determine its type and generate a corresponding OpenAPI SchemaObject.
-It handles basic Go types (string, int, bool, etc.), structs, slices/arrays, and maps.
-For structs, it generates named schemas and stores them in schemaCtx.componentsSchemas to allow for reuse via $ref pointers, preventing duplication and handling circular dependencies.
flowchart TD
- GS_START["Start generateSchema(instance, schemaCtx)"] --> GS1{"Handle nil/ptr, Get reflect.Type & Value"}
- GS1 --> GS2_CHECK_CACHE{"Struct & Already Generated (in schemaCtx)?"}
- GS2_CHECK_CACHE -- Yes --> GS_RETURN_REF["Output: SchemaObject with $ref"]
-
- GS2_CHECK_CACHE -- No --> GS3_SWITCH_KIND{"Switch on Type Kind"}
- GS3_SWITCH_KIND -- Struct --> GS_STRUCT["Handle Struct"]
- GS_STRUCT --> GS_STRUCT_TIME{"time.Time?"}
- GS_STRUCT_TIME -- Yes --> GS_TIME_SCHEMA["Set type:string, format:date-time"]
- GS_STRUCT_TIME -- No --> GS_STRUCT_NAME["Generate/Get Unique Schema Name"]
- GS_STRUCT_NAME --> GS_STRUCT_RESERVE["Add to schemaCtx (initially nil for cycle breaking)"]
- GS_STRUCT_RESERVE --> GS_STRUCT_PROPS["Iterate Fields: Get JSON name, recursively call generateSchema for field type, add to Properties"]
- GS_STRUCT_PROPS --> REF_GS_FIELD["(Recursive calls use 'Flow: generateSchema')"]
- REF_GS_FIELD --> GS_STRUCT_REQ["Determine Required Fields"]
- GS_STRUCT_REQ --> GS_STRUCT_STORE["Store final schema in schemaCtx"]
- GS_STRUCT_STORE --> GS_RETURN_REF
-
- GS3_SWITCH_KIND -- Slice/Array --> GS_ARRAY["Handle Slice/Array: Set type:array, recursively call generateSchema for Items"]
- GS_ARRAY --> REF_GS_ITEMS["(Recursive call uses 'Flow: generateSchema')"]
- REF_GS_ITEMS --> GS_SCHEMA_BUILT
-
- GS3_SWITCH_KIND -- Map --> GS_MAP["Handle Map: Set type:object, recursively call generateSchema for AdditionalProperties"]
- GS_MAP --> REF_GS_ADD_PROPS["(Recursive call uses 'Flow: generateSchema')"]
- REF_GS_ADD_PROPS --> GS_SCHEMA_BUILT
-
- GS3_SWITCH_KIND -- Primitives (string, int, bool, etc.) --> GS_PRIMITIVE["Handle Primitives: Set type & format"]
- GS_PRIMITIVE --> GS_SCHEMA_BUILT
-
- GS3_SWITCH_KIND -- Other/Unsupported --> GS_UNSUPPORTED["Handle Unsupported: Log warning, set basic object type"]
- GS_UNSUPPORTED --> GS_SCHEMA_BUILT
-
- GS_TIME_SCHEMA --> GS_SCHEMA_BUILT
- GS_SCHEMA_BUILT["Schema Object Constructed (without $ref)"] --> GS_OUTPUT_DIRECT["Output: SchemaObject"]
-
- GS_OUTPUT_DIRECT --> GS_END["End generateSchema"]
- GS_RETURN_REF --> GS_END
-
-ServeSwaggerUIThis function sets up HTTP handlers to serve the embedded Swagger UI static assets. It handles requests for the UI’s root path (redirecting if necessary), the index.html file, and other assets like CSS and JavaScript files, serving them from the embedded filesystem.
flowchart TD
- SSUI_START["Start ServeSwaggerUI(prefix)"] --> SSUI1["Get Sub-Filesystem for embedded 'swagger-ui' assets"]
- SSUI1 -- Error --> SSUI_PANIC["Panic: Failed to locate assets"]
- SSUI1 -- Success --> SSUI2_HANDLE_ROOT["Register Handler for 'prefix': Redirects to 'prefix/'"]
- SSUI2_HANDLE_ROOT --> SSUI3_HANDLE_INDEX["Register Handler for 'prefix/': Serves 'index.html' from embedded FS"]
- SSUI3_HANDLE_INDEX --> SSUI4_HANDLE_ASSETS["Register Handler for 'prefix/{file}': Serves other static assets (CSS, JS, etc.) from embedded FS with correct Content-Type"]
- SSUI4_HANDLE_ASSETS --> SSUI5_LOG["Log Swagger UI served"]
- SSUI5_LOG --> SSUI_END["End ServeSwaggerUI"]
-
-
- With nova, you can use the rc.Bind() method on the ResponseContext to automatically decode JSON from the request body into your struct. This avoids manual decoding and error handling boilerplate.
Here is a complete example using PostFunc and rc.Bind():
package main
-
-import (
- "log"
- "net/http"
-
- "github.com/xlc-dev/nova/nova"
-)
-
-// MyData defines the structure of our expected JSON payload.
-type MyData struct {
- Name string `json:"name"`
- Value int `json:"value"`
-}
-
-// handleJsonRequest uses nova's ResponseContext to simplify data binding and response.
-func handleJsonRequest(rc *nova.ResponseContext) error {
- var data MyData
-
- // Bind the incoming JSON request body to the 'data' struct.
- if err := rc.BindJSON(&data); err != nil {
- // If binding fails (e.g., malformed JSON), return a 400 Bad Request.
- log.Printf("Error binding JSON: %v", err)
- return rc.JSONError(http.StatusBadRequest, "Invalid JSON payload")
- }
-
- log.Printf("Received data: Name=%s, Value=%d\n", data.Name, data.Value)
-
- // Send a success response using the JSON helper.
- response := map[string]any{
- "status": "success",
- "received_name": data.Name,
- }
- return rc.JSON(http.StatusOK, response)
-}
-
-func main() {
- router := nova.NewRouter()
- router.PostFunc("/data", handleJsonRequest)
-
- log.Println("Server starting on :8080")
- if err := http.ListenAndServe(":8080", router); err != nil {
- log.Fatalf("Server failed to start: %v", err)
- }
-}
-
-GET requests typically pass data in two ways: URL query parameters (e.g., ?id=123) or path parameters (e.g., /items/123). nova makes handling both easy.
For query parameters, you can access the standard http.Request object via rc.Request() and use its URL.Query() method.
// handleGetWithQuery processes data from URL query parameters.
-// Example request: GET /items?name=widget&id=123
-func handleGetWithQuery(rc *nova.ResponseContext) error {
- // Access the underlying request to get query parameters.
- queryParams := rc.Request().URL.Query()
-
- name := queryParams.Get("name")
- idStr := queryParams.Get("id")
-
- if name == "" || idStr == "" {
- return rc.JSONError(http.StatusBadRequest, "Missing 'name' or 'id' query parameter")
- }
-
- log.Printf("Processed query: name=%s, id=%s\n", name, idStr)
-
- response := map[string]string{
- "item_name": name,
- "item_id": idStr,
- }
- return rc.JSON(http.StatusOK, response)
-}
-
-// In main():
-// router.GetFunc("/items", handleGetWithQuery)
-
-For path parameters defined in your route (e.g., /{id}), nova provides the much cleaner rc.URLParam() helper.
// handleGetWithPathVar processes data from a URL path parameter.
-// Example request: GET /users/42
-func handleGetWithPathVar(rc *nova.ResponseContext) error {
- // Use the URLParam helper to get the 'id' from the path.
- userID := rc.URLParam("id")
-
- log.Printf("Processed request for user ID: %s\n", userID)
-
- response := map[string]string{
- "status": "found",
- "user_id": userID,
- }
- return rc.JSON(http.StatusOK, response)
-}
-
-// In main():
-// router.GetFunc("/users/{id}", handleGetWithPathVar)
-
-Nova has powerful, built-in validation. Instead of rc.Bind(), use rc.BindValidated(). It automatically binds the data and then runs validations based on the struct tags you’ve defined (required, minlength, format, etc.).
If validation fails, it returns a detailed error, which you can send back to the client.
-package main
-
-import (
- "log"
- "net/http"
-
- "github.com/xlc-dev/nova/nova"
-)
-
-// UserSignup defines a struct with validation tags.
-type UserSignup struct {
- Username string `json:"username" minlength:"3" maxlength:"20" format:"alphanumeric"`
- Email string `json:"email" format:"email"`
- // omitempty is not used, so this field is required by default.
- Password string `json:"password" format:"password"`
-}
-
-// handleUserSignup binds and validates the request body in one step.
-func handleUserSignup(rc *nova.ResponseContext) error {
- var user UserSignup
-
- // Bind AND validate the incoming JSON.
- if err := rc.BindValidated(&user); err != nil {
- // e.g., "validation failed: Field 'username' must contain only alphanumeric characters"
- log.Printf("Validation failed: %v", err)
- return rc.JSONError(http.StatusBadRequest, err.Error())
- }
-
- log.Printf("Successfully validated and created user: %s", user.Username)
-
- response := map[string]string{
- "status": "user_created",
- "username": user.Username,
- }
- return rc.JSON(http.StatusCreated, response)
-}
-
-func main() {
- router := nova.NewRouter()
- router.PostFunc("/signup", handleUserSignup)
-
- log.Println("Server starting on :8080")
- if err := http.ListenAndServe(":8080", router); err != nil {
- log.Fatalf("Server failed to start: %v", err)
- }
-}
-
-
- Welcome to Nova.
-Nova is a flexible framework that simplifies creating both RESTful APIs and web UIs. -It extends Go’s standard library with sensible defaults and helper utilities for components like routing, middleware, OpenAPI, and HTML templating, minimizing decision fatique. -Making it easier than ever to build powerful web applications in Go.
-Nova is designed to be a lightweight framework that has as little dependencies as possible. -It is built on top of Go’s standard library, making it easy to integrate with existing Go codebases and libraries.
-Together with the CLI tool, Nova provides a streamlined development experience for building web applications.
-nova new.nova binary.To get started with Nova, read the Quickstart guide, or check out the example project.
-This project is licensed under the MIT License.
- -