Prologue Web Framework

repository·devel·Indexed 23 days ago

https://github.com/planety/prologue

A high-performance, flexible web framework written in Nim designed to reduce 'magic' and surprises in web service development. It features a comprehensive set of core capabilities including routing, context management, and middleware, alongside plugins for I18n, WebSockets, session management, and CSRF protection. Prologue supports flexible configuration via .env or JSON files and can be used with asyncdispatch or the chronos backend via kairos.

Tokens
21.3K
Snippets
91
Records
118
Agent score
80%

What's inside Prologue

  1. Overview of Prologue features

    devel

    Prologue provides a comprehensive set of features for web development, divided into Core and Plugin modules:

    Core Features:

    • Configuration and Settings
    • Context management
    • Param, Query, and Form Data handling
    • Static Files
    • Middleware support
    • Routing system (based on nest)
    • Cookie management
    • Startup and Shutdown events
    • URL Building
    • Error Handling

    Plugin Features:

    • I18n (Internationalization)
    • Basic Authentication
    • Minimal OpenAPI support
    • Websocket support
    • Mocking for testing
    • CORS Response
    • Data Validation
    • Session management
    • Cache
    • Signing
    • Command line tools
    • CSRF (Cross-Site Request Forgery) protection
    • Clickjacking Protection
  2. Understand startup and shutdown events in Prologue

    devel

    Prologue provides two types of lifecycle events that you can register when initializing an application:

    • startup events: Executed exactly once for each thread during the application's initialization phase.
    • shutdown events: Executed once after the main loop has finished, typically used for cleanup.

    Events can be either synchronous or asynchronous. The system automatically handles the distinction based on the function pointer type you provide to initEvent.

  3. Handle HTTP 500 internal errors

    devel

    HTTP 500 errors indicate internal framework errors. The behavior depends on your settings:

    • Debug Mode (settings.debug = true): The framework will send exception messages directly to the web browser if there are error messages available.
    • Production Mode: The framework uses the registered default error handler. You can override this behavior by registering your own custom handler for Http500 using app.registerErrorHandler(Http500, yourHandler).
  4. How middlewares work in Prologue

    devel

    Middlewares in Prologue follow an 'onion' model. A request passes through a sequence of middlewares before reaching the final handler. After the handler executes, the response passes back through the same middlewares in reverse order.

    To allow the request to proceed to the next middleware or the final handler, you must call await switch(ctx).

    • Before logic: Code placed before await switch(ctx) executes as the request enters the middleware.
    • After logic: Code placed after await switch(ctx) executes as the response passes back through the middleware.
    proc myDebugRequestMiddleware*(appName = "Prologue"): HandlerAsync = 
      result = proc(ctx: Context) {.async.} = 
        logging.info "debugRequestMiddleware->begin" # do something before
        await switch(ctx)
        logging.info "debugRequestMiddleware->End"   # do something after
  5. Structure a Prologue application with separated routing and views

    devel

    For a clean separation of concerns, you can split your application logic into different modules:

    1. urls.nim: Responsible for mapping URLs (e.g., /) to specific handler procedures (procs).
    2. views.nim: Contains the controller/handler procedures (procs) that process incoming HTTP requests.
    3. app.nim: The main entry point that loads settings, initializes the Prologue application, attaches routes, and starts the server.
  6. Use Wildcards and Greedy Matching

    devel

    Prologue provides two types of wildcard matching:

    1. Wildcard (*): Matches exactly one URL section. For example, /static/* matches /static/css but not /static/css/style.css.
    2. Greedy ($): Matches all remaining URL sections. The greedy character must be placed at the end of the URL. Using it in the middle of a URL will raise a RouteError. For example, /test/{param}$ matches /test/foo/bar/baz where the parameter is foo/bar/baz.
    import prologue
    
    proc hello*(ctx: Context) {.async.} = 
      resp "Hello, Prologue"
    
    var app = newApp()
    app.get("/static/*", hello)
    app.get("/test/{param}$", hello)
    app.get("/test/static/*$", hello)
    app.run()
  7. Extend the request Context with custom data

    devel
    You can extend the default Context of a request (which contains the HTTP request and server settings) by defining a custom type. This is useful for adding application-specific data, such as user login information, to every request via middleware. In the example, the Context is extended to a DataContext which includes a new field id.
  8. How sessions work in Prologue

    devel
    Sessions allow you to store user state across multiple requests. To use sessions or flash messages, you must first register the sessionMiddleware in your application's global or handler-specific middlewares. Prologue provides different session implementations depending on your storage needs (Signed Cookies, Memory, or Redis).
  9. Structure of Karax DSL templates in Prologue

    devel

    Templates in Prologue are written using the Karax DSL. Each template file follows a specific pattern using two types of procedures:

    1. Page Procs: Procedures ending with the suffix Page (e.g., indexPage, loginPage). These act as the final, top-level template structure for the page.
    2. Section Procs: Procedures ending with the suffix Section. This is where the actual template layout and logic are implemented.

    To reduce code duplication, you can store reusable blocks (chunks or partials) in a share subfolder and call them within your templates.

  10. Organize Prologue application files

    devel

    A standard Prologue application structure typically includes the following components:

    • static/: Stores all public assets.
    • templates/: Contains Karax DSL template files.
    • .env: Holds environment-specific configuration values.
    • app.nim: The application entry point where the Prologue app is initialized.
    • urls.nim: Groups URL endpoints and maps them to specific procedures in views.nim.
    • views.nim: Contains the business logic (similar to controllers or routes) called by the URLs.
    • consts.nim: Used for storing application constants like database or schema file paths.
    • initdb.nim & schema.sql: Used for database initialization (e.g., creating a SQLite file from a schema if it doesn't exist).
    • *.db: The SQLite database file (e.g., blog.db).
  11. Understand the Context abstraction

    devel

    The Context object is initialized whenever a new request enters the system. It serves as the primary interface for handlers to access request information and manage responses. You can access attributes such as request, response, and session through the ctx object passed to your handlers.

    For example, you can inspect the HTTP method of an incoming request using ctx.request.reqMethod.

    proc login*(ctx: Context) {.async.} =
      doAssert ctx.request.reqMethod == HttpPost
  12. Extend the Context object

    devel

    To add custom data to a request, you must define a new type that inherits from the Context object. To ensure these custom attributes are initialized, you must implement the extend method on your custom type. Finally, register your custom context type with the application using app.run(YourCustomContext).

    import prologue
    
    type
      UserContext = ref object of Context
        data: int
    
    # initialize data
    method extend(ctx: UserContext) {.gcsafe.} =
      ctx.data = 999
    
    proc hello*(ctx: Context) {.async.} =
      let ctx = UserContext(ctx)
      doAssert ctx.data == 999
      resp "<h1>Hello, Prologue!</h1>"
    
    var app = newApp()
    app.get("/", hello)
    app.run(UserContext)