Wiki
Download
Manual
Eggs
API
Tests
Bugs
show
edit
history
You can edit this page using
wiki syntax
for markup.
Article contents:
== Schematra A modern web framework for CHICKEN Scheme inspired by Sinatra that combines simplicity with power. === Description Schematra is a lightweight, expressive web framework for CHICKEN Scheme built on top of Spiffy that brings the elegance of Lisp to web development. It features minimal boilerplate, Chiccup HTML rendering, flexible routing, built-in sessions, and extensible middleware support. === Requirements * CHICKEN Scheme 5.0 or later * Dependencies: spiffy, format, openssl, message-digest, hmac, sha2, http-client, medea, nrepl === Installation <enscript highlight="bash"> chicken-install schematra </enscript> '''SRFI-1 Compatibility:''' If you're using SRFI-1 and need its {{delete}} function alongside Schematra's HTTP DELETE verb, rename one on import: <enscript highlight="scheme"> ;; Rename SRFI-1's delete (recommended) (import (rename srfi-1 (delete srfi1:delete)) schematra chiccup) (delete "/users/:id" ...) ; HTTP DELETE route (srfi1:delete 'x my-list) ; Remove items from list </enscript> === Basic Usage <enscript highlight="scheme"> (import schematra chiccup) ;; Create app instance (define app (schematra/make-app)) ;; Define routes within app context (with-schematra-app app (get "/" (ccup->html `[h1 "Hello, Schematra!"])) (post "/submit" "Form submitted!") ;; Install and start server (schematra-install) (schematra-start)) </enscript> === API ==== App Management <procedure>(schematra/make-app [config])</procedure> Creates a new isolated Schematra application instance. Each app has its own routes and middleware. <enscript highlight="scheme"> (define app (schematra/make-app)) </enscript> <syntax>(with-schematra-app app body ...)</syntax> Sets the current app context for route and middleware definitions. <enscript highlight="scheme"> (with-schematra-app app (get "/" "Hello!") (use-middleware! my-middleware) (schematra-install) (schematra-start)) </enscript> <parameter>(current-app [app])</parameter> Get or set the current application instance. Used internally by route macros and middleware functions. ==== Route Definition <syntax>(get route body ...)</syntax> <syntax>(post route body ...)</syntax> <syntax>(put route body ...)</syntax> <syntax>(delete route body ...)</syntax> Define HTTP route handlers for the given path. Routes must be defined within a {{with-schematra-app}} context. Route can contain URL parameters using {{:param}} syntax. <enscript highlight="scheme"> (with-schematra-app app ;; Simple routes (get "/" "Home page") (get "/about" (ccup->html `[h1 "About Us"])) ;; URL parameters (get "/user/:id" (let ((id (alist-ref "id" (current-params) equal?))) (ccup->html `[h1 ,(string-append "User " id)])))) </enscript> <parameter>(current-params)</parameter> Returns an association list containing the current request parameters: * '''Path parameters''' (string keys): URL segments starting with ':' * '''Query parameters''' (symbol keys): URL query string parameters * '''Form data''' (symbol keys): POST form fields (requires body-parser-middleware) This parameter only contains meaningful data inside a route. Outside it will default to {{#f}}. <enscript highlight="scheme"> ;; For route /users/:id and request /users/123?format=json (current-params) ; => '(("id" . "123") (format . "json")) ;; Access path parameter (alist-ref "id" (current-params) equal?) ; => "123" ;; Access query parameter (alist-ref 'format (current-params)) ; => "json" </enscript> <procedure>(halt status [body] [headers])</procedure> Immediately stop request processing and send an HTTP response. <enscript highlight="scheme"> (halt 'not-found "Page not found") (halt 'bad-request "{\"error\": \"Invalid input\"}" '((content-type application/json))) </enscript> <procedure>(redirect location [status])</procedure> Redirect the client to a different URL. Default status is {{found}} (302). <enscript highlight="scheme"> (redirect "/login") (redirect "/new-location" 'moved-permanently) </enscript> <procedure>(send-json-response datum [status])</procedure> Send a JSON response with proper content-type headers. Default status is {{ok}} (200). <enscript highlight="scheme"> (send-json-response '((status . "healthy") (version . "1.0"))) (send-json-response '((error . "Not found")) 'not-found) </enscript> <procedure>(static path directory)</procedure> Serve static files from {{directory}} at URL {{path}} prefix. <procedure>(schematra-install)</procedure> Install the schematra route handlers. <procedure>(schematra-start #!key development? nrepl?)</procedure> Start the web server. * {{development?}}: Enable development mode (default: #f) * {{nrepl?}}: Start NREPL server in dev mode (default: #t) ==== Response Format Route handlers can return: '''String Response:''' Returns 200 OK with the string as body <enscript highlight="scheme"> (get "/hello" "Hello, World!") </enscript> '''Response Tuple:''' Format {{(status body [headers])}} for full control <enscript highlight="scheme"> '(created "User created successfully") '(ok "{\"message\": \"success\"}" ((content-type application/json))) </enscript> === Chiccup Rendering <procedure>(ccup->html s-html)</procedure> <procedure>(ccup->sxml s-html)</procedure> Convert Chiccup list to HTML string or SXML. Chiccup allows writing HTML using Lisp syntax with CSS class integration. Behind the scenes, chiccup converts the simplified chiccup-style lists to SXML and then to HTML using sxml-transforms. You can use {{ccup->sxml}} to get the intermediate SXML representation and leverage the full power of the sxml-transforms ecosystem for advanced transformations. <enscript highlight="scheme"> ;; Basic elements `[h1 "Hello World"] ;; CSS classes and IDs - default element is "div" ;; Note: ID (#id) must come after classes `[.container.mx-auto#main "Content"] ;; => <div class="container mx-auto" id="main">Content</div> ;; Attributes with @ syntax `[a (@ (href "/page")) "Link"] `[input (@ (type "text") (name "username"))] ;; Attribute values are automatically HTML-escaped (like React/Vue) `[div (@ (data-config "{\"theme\":\"dark\"}")) "Content"] ;; => <div data-config="{"theme":"dark"}">Content</div> ;; Boolean attributes (no values) `[input (@ (type "checkbox") (checked) (disabled))] ;; => <input type="checkbox" checked disabled> ;; Conditional attributes (let ((is-disabled #t)) `[button (@ (type "submit") ,@(if is-disabled '((disabled)) '())) "Submit"]) ;; => <button type="submit" disabled>Submit</button> ;; Dynamic content `[ul ,@(map (lambda (item) `[li ,item]) items)] ;; Raw HTML (unescaped) `[div (raw "<em>emphasized</em>")] </enscript> === Middleware System <procedure>(use-middleware! middleware-function)</procedure> Install middleware functions that process requests before route handlers. Must be called within a {{with-schematra-app}} context. Middleware signature: <enscript highlight="scheme"> (define (my-middleware next) ;; Process request before handler (let ((response (next))) ; Call next middleware or handler ;; Process response after handler response)) </enscript> Middleware can access and modify request parameters using {{(current-params)}} and {{(current-params new-params)}}. ==== Session Middleware <enscript highlight="scheme"> (import schematra-session) (with-schematra-app app (use-middleware! (session-middleware "secret-key")) ;; Session functions (session-get "key" [default]) (session-set! "key" value) (session-delete! "key") (session-destroy!)) </enscript> ==== Body Parser Middleware <enscript highlight="scheme"> (import schematra-body-parser) (with-schematra-app app (use-middleware! (body-parser-middleware)) ;; Automatically parses form data and adds to (current-params) ;; Form fields become symbol keys (post "/login" (let ((username (alist-ref 'username (current-params)))) ;; Handle login... ))) </enscript> === OAuth2 Authentication (Oauthtoothy) Oauthtoothy provides complete OAuth2 authentication integration. <procedure>(oauthtoothy-middleware providers #!key success-redirect save-proc load-proc)</procedure> Creates OAuth2 authentication middleware. '''Parameters:''' * {{providers}}: List of OAuth2 provider configurations * {{success-redirect}}: URL to redirect after successful auth (default: "/") * {{save-proc}}: Function to save user data (optional) * {{load-proc}}: Function to load user data (optional) <parameter>(auth-base-url [url])</parameter> Base URL for OAuth2 callback URLs. <parameter>(current-auth)</parameter> Current user's authentication state. Returns association list with: * {{authenticated?}}: Boolean indicating if user is logged in * {{user-id}}: Unique identifier for the user * Additional user data from provider ==== Provider Configuration Each provider is an association list with required keys: <enscript highlight="scheme"> (define (google-provider #!key client-id client-secret) `((name . "google") (client-id . ,client-id) (client-secret . ,client-secret) (auth-url . "https://accounts.google.com/o/oauth2/auth") (token-url . "https://oauth2.googleapis.com/token") (user-info-url . "https://www.googleapis.com/oauth2/v2/userinfo") (scopes . "profile email") (user-info-parser . ,parse-google-user))) </enscript> ==== Complete OAuth2 Example <enscript highlight="scheme"> (import schematra schematra-session oauthtoothy chiccup) ;; User data parser (define (parse-google-user json-response) `((id . ,(alist-ref 'id json-response)) (name . ,(alist-ref 'name json-response)) (email . ,(alist-ref 'email json-response)))) ;; Create app (define app (schematra/make-app)) (with-schematra-app app ;; Install middleware (use-middleware! (session-middleware "secret-key")) (use-middleware! (oauthtoothy-middleware (list (google-provider client-id: (get-environment-variable "GOOGLE_CLIENT_ID") client-secret: (get-environment-variable "GOOGLE_CLIENT_SECRET"))) success-redirect: "/profile")) ;; Protected route (get "/profile" (let ((auth (current-auth))) (if (alist-ref 'authenticated? auth) (ccup->html `[h1 ,(string-append "Welcome, " (alist-ref 'name auth))]) (redirect "/auth/google")))) ;; Logout (get "/logout" (session-destroy!) (redirect "/")) (schematra-install) (schematra-start)) </enscript> === Examples ==== Simple Web App with Sessions <enscript highlight="scheme"> (import schematra chiccup schematra-session) (define app (schematra/make-app)) (with-schematra-app app (use-middleware! (session-middleware "secret-key")) (get "/" (let ((user (session-get "username"))) (if user (ccup->html `[h1 ,(format "Welcome back, ~a!" user)]) (redirect "/login")))) (get "/login" (ccup->html `[form (@ (method "POST") (action "/login")) [input (@ (type "text") (name "username") (placeholder "Username"))] [button "Login"]])) (post "/login" (let ((username (alist-ref 'username (current-params)))) (session-set! "username" username) (redirect "/"))) (schematra-install) (schematra-start)) </enscript> ==== JSON API <enscript highlight="scheme"> (define app (schematra/make-app)) (with-schematra-app app (get "/api/users" (send-json-response `((users . ,(map user->alist (get-all-users))) (count . ,(length (get-all-users)))))) (post "/api/users" (let ((name (alist-ref 'name (current-params))) (email (alist-ref 'email (current-params)))) (if (and name email) (let ((user-id (create-user! name email))) (send-json-response `((id . ,user-id) (created . #t)) 'created)) (send-json-response '((error . "Invalid input")) 'bad-request)))) (schematra-install) (schematra-start)) </enscript> ==== Testing Routes <enscript highlight="scheme"> (import test schematra uri-common intarweb) ;; Create isolated test app (define test-app (schematra/make-app)) ;; Define routes in test app (with-schematra-app test-app (get "/hello" "Hello, World!") (get "/users/:id" (let ((id (alist-ref "id" (current-params) equal?))) (format "User ~a" id))) (use-middleware! (lambda (next) (let ((result (next))) ;; Middleware can transform responses (if (string? result) (string-append "[middleware] " result) result))))) ;; Helper: create mock request (define (make-mock-request method path) (make-request method: method uri: (uri-reference path) headers: (headers '()))) ;; Run tests with the test egg (test-group "Schematra Routes" (test "GET /hello returns greeting" "[middleware] Hello, World!" (with-schematra-app test-app (parameterize ((current-request (make-mock-request 'GET "/hello")) (current-response (make-response))) (schematra-route-request (current-request)) (current-body)))) (test "GET /users/:id extracts params correctly" "[middleware] User 123" (with-schematra-app test-app (parameterize ((current-request (make-mock-request 'GET "/users/123")) (current-response (make-response))) (schematra-route-request (current-request)) (current-body)))) (test "404 on unknown route" #f (with-schematra-app test-app (parameterize ((current-request (make-mock-request 'GET "/unknown")) (current-response (make-response))) ;; Returns #f when route not found (schematra-route-request (current-request)))))) ;; Benefits: ;; - No HTTP server needed - tests run in milliseconds ;; - Complete isolation - each test gets its own app ;; - Test middleware behavior independently ;; - Verify params, routing, and response bodies </enscript> === License Copyright © 2025 Rolando Abarca. Released under the GNU General Public License v3.0. === Repository & full docs [[https://github.com/schematra/schematra|GitHub Repository]] [[https://github.com/schematra/schematra/blob/main/docs/docs.md|Full docs]]
Description of your changes:
I would like to authenticate
Authentication
Username:
Password:
Spam control
What do you get when you multiply 3 by 0?