smol
Tip: Hover the links for short summaries!
Builder
new, port, random_port, bind, bind_all, after_start, on_error
Server Operations
start get_port, get_interface stop
Request Handling
read_bytes_tree, read_bytes, read_string, read_json, read_form, read_multipart, handle_head, cache, cache_with, enable_cross_origin_requests, csrf_known_header_protection, content_security_policy_protection, require_method, require_content_type
Response Creation
send_file, send_bytes, send_chunked, send_html, send_json, send_string, send_status, redirect, moved_permanently, server_sent_events, websocket, with_status, last_modified, with_extra_headers, as_file_download, send, step, close
Advanced
adapt, adapt_node, detect_runtime, rescue status_text
Types
Configuration for the enable_cross_origin_requests middleware.
origins must contain trusted origin strings such as
"https://app.example.com". Origins are compared literally. This middleware
does not expand wildcard or suffix patterns. The string "*" has no
special meaning and only matches an origin header with that exact value.
pub type CrossOriginPolicy {
CrossOriginPolicy(
origins: List(String),
methods: List(http.Method),
headers: List(String),
expose: List(String),
max_age: option.Option(Int),
credentials: Bool,
)
}
Constructors
-
CrossOriginPolicy( origins: List(String), methods: List(http.Method), headers: List(String), expose: List(String), max_age: option.Option(Int), credentials: Bool, )Arguments
- origins
-
Exact origins that are allowed to read responses.
- methods
-
Methods that are allowed for cross-origin requests.
- headers
-
Request headers that are allowed in cross-origin preflight requests.
- expose
-
Response headers that browsers may expose to JavaScript.
- max_age
-
Optional
Access-Control-Max-Agefor accepted preflight requests. - credentials
-
Whether cookies and other credentials are allowed.
pub type FileError {
IsDir
NoAccess
NoEntry
UnknownFileError
RuntimeNotSupportedFileError
}
Constructors
-
IsDir -
NoAccess -
NoEntry -
UnknownFileError -
RuntimeNotSupportedFileError
A parsed multipart form.
pub type FormData {
FormData(
values: List(#(String, String)),
files: List(#(String, UploadedFile)),
)
}
Constructors
-
FormData( values: List(#(String, String)), files: List(#(String, UploadedFile)), )
smol uses a standard web ReadableStream under the hood on all runtimes,
including Node.js!
pub type ReadableStream =
@internal ReadableStream
A convenience alias for a HTTP request with a ReadableBody as the body.
pub type Request =
request.Request(ReadableStream)
A convenience alias for a HTTP response with a ReadableBody as the body.
pub type Response =
response.Response(ReadableStream)
pub type SseEvent {
SseEvent(
event: option.Option(String),
data: String,
id: option.Option(String),
retry: option.Option(Int),
)
}
Constructors
-
SseEvent( event: option.Option(String), data: String, id: option.Option(String), retry: option.Option(Int), )
A type used in various functions as a return type from “unfold” or “update”
- style functions, usually used in combination with a
Promise, similar toactor.Nextoryielder.Step.
Represents the result of a stateful iteration that can send messages back to a client.
pub type Step(state, output) {
Close
Step(state: state)
Send(state: state, sending: output)
}
Constructors
-
CloseStop iterating, close the connection. We are done sending.
-
Step(state: state)Loop, without sending anything.
-
Send(state: state, sending: output)Loop, sending
returningto the client.
pub type UploadedFile {
UploadedFile(file_name: String, path: String)
}
Constructors
-
UploadedFile(file_name: String, path: String)
pub type WebsocketMessage {
Binary(bytes: BitArray)
Text(text: String)
}
Constructors
-
Binary(bytes: BitArray) -
Text(text: String)
Values
pub fn adapt(
handler: fn(request.Request(ReadableStream)) -> promise.Promise(
response.Response(ReadableStream),
),
) -> fn(dynamic.Dynamic) -> promise.Promise(dynamic.Dynamic)
Adapts a smol handler function to work with standard Web Request/Response objects.
This is useful for integrating with other JavaScript frameworks or environments that use the standard Fetch API, like serverless platforms or service workers.
pub fn adapt_node(
handler: fn(request.Request(ReadableStream)) -> promise.Promise(
response.Response(ReadableStream),
),
) -> fn(dynamic.Dynamic, dynamic.Dynamic) -> Nil
Adapts a smol handler function to work with NodeJS.
This can be useful for integrating Gleam and smol into existing Node projects, or for legacy environments where Node is assumed.
pub fn after_start(
builder: Builder,
after_start: fn(Int, String) -> Nil,
) -> Builder
Sets a callback function to be called after the server starts.
The function receives the actual port and interface the server is listening
on. By default, a simple Listening on... message is printed.
Example
fn log_start(port, interface) {
io.println("Server started on " <> interface <> ":" <> int.to_string(port))
}
smol.new(handler)
|> smol.after_start(log_start)
pub fn as_file_download(
response: promise.Promise(response.Response(ReadableStream)),
named name: String,
) -> promise.Promise(response.Response(ReadableStream))
Convenience function to send a response as a file download.
Sets the Content-Disposition header to attachment using the given file
name.
Example
smol.send_string("{\"ok\":true}")
|> smol.as_file_download(named: "blob.json")
pub fn bind(builder: Builder, interface: String) -> Builder
Sets the network interface to listen on.
By default smol listens on "127.0.0.1" (localhost).
Example
smol.new(handler)
|> smol.bind("192.168.1.100")
pub fn bind_all(builder: Builder) -> Builder
Configures the server to listen on all network interfaces (0.0.0.0).
Example
smol.new(handler)
|> smol.bind_all()
pub fn cache(
from request: request.Request(ReadableStream),
then next: fn() -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Handles conditional cache requests.
This middleware adds Cache-Control: no-cache when the response does not
already have a Cache-Control header, and handles If-None-Match and
If-Modified-Since request headers using the response’s ETag and
Last-Modified headers.
For expensive responses where you can compute the validators before
rendering the body, use cache_with to allow the middleware
to return 304 Not Modified before calling the handler.
Example
fn handler(request) {
use <- smol.cache(request)
use _error <- smol.send_file(
"./priv/public/index.html",
offset: 0,
limit: option.None,
)
smol.send_status(404)
}
pub fn cache_with(
from request: request.Request(ReadableStream),
etag etag: option.Option(String),
last_modified last_modified: option.Option(timestamp.Timestamp),
then next: fn() -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Handles conditional cache requests with explicit validators.
This is the same as cache, but it can return 304 Not Modified
before calling the handler when the request validators match the explicit
etag or last_modified values.
The final response is still checked after the handler runs, using its
ETag and Last-Modified headers. If the final response is missing one of
the explicit validators, it is added automatically.
Example
fn handler(request) {
use <- smol.cache_with(
request,
etag: option.None,
last_modified: option.Some(post.updated_at),
)
render_post(post)
}
pub fn close() -> promise.Promise(Step(state, output))
A convenience method useful for async functions.
Equivalent to promise.resolve(Close)
pub fn content_security_policy_protection(
then handle_request: fn(String) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Protects against cross-site-scripting (XSS) attacks using a nonce-based content-security-policy (CSP).
This middleware will provide a unique single use random string (a nonce) to the handler, and set this CSP header on the response returned by the handler.
Content-Security-Policy:
script-src 'nonce-{NONCE}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
This header causes the browser to be restricted in these ways:
-
Any
<script>tag without anonce="..."property set to the nonce for this request will not be executed. Any scripts created by scripts with the correctnonceproperty will be executed. -
Any inline JavaScript event handlers on elements will not be evaluated. e.g.
<span onclick="doSomething();">Click me</span>will do nothing when clicked. -
Any
<object>or<embed>elements will not be executed. -
Any use of
<base>to change the base for relative URLs will be prevented.
When using this middleware be sure to add the nonce="..." property to all
<script> elements.
use csp_nonce <- smol.content_security_policy_protection()
<script type="module" nonce="RENDER_YOUR_CSP_NONCE_HERE">
console.log("Hello, Joe!")
</script>
It is recommended to add this middleware so that it applies to all routes in your application.
For more information about CSP see these articles:
pub fn csrf_known_header_protection(
from request: request.Request(ReadableStream),
then next: fn(request.Request(ReadableStream)) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Protects against Cross-Site Request Forgery (CSRF) attacks by checking the
host request header against the origin header or referer header.
- Requests with the
GetandHeadmethods are accepted. - Requests with no
hostheader are rejected with status 400: Bad Request. - Requests with no
originorrefererheaders are accepted, but have thecookieheader removed to prevent CSRF attacks against cookie based sessions. - Requests with origin/referer headers that match their host header are accepted.
- Requests with headers that don’t match are rejected with status 400: Bad Request.
This middleware implements the OWASP Verifying Origin With Standard Headers
CSRF defense-in-depth technique. Do not allow Get or Head requests
to trigger side effects if relying only on this function and the SameSite
cookies feature for CSRF protection.
This middleware and SameSite cookies typically is sufficient to protect against CSRF attacks, but you may decide to employ token based mitigation for more complete CSRF defence-in-depth.
If you have routes that should be accessible from other origins then you should not use this middleware for those routes.
Example
fn handler(request) {
use request <- smol.csrf_known_header_protection(request)
// ...
}
pub fn detect_runtime() -> Result(Runtime, Nil)
Detects the current JavaScript runtime environment.
Returns a Result containing the Runtime or an error if the runtime couldn’t be detected.
Example
fn main() {
case smol.detect_runtime() {
Ok(smol.Node) -> io.println("Running on Node.js")
Ok(smol.Deno) -> io.println("Running on Deno")
Ok(smol.Bun) -> io.println("Running on Bun")
Error(_) -> io.println("Unknown runtime")
}
}
pub fn enable_cross_origin_requests(
from request: request.Request(ReadableStream),
using policy: CrossOriginPolicy,
then next: fn(request.Request(ReadableStream)) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Enables cross-origin requests.
This middleware adds CORS response headers for requests that match the given
policy. Requests that do not match the policy are passed through without
CORS allow headers, so browsers will not expose their responses to the
calling JavaScript. Preflight requests are answered directly with an empty
204 response.
CORS controls whether browsers expose responses to scripts from other origins. It does not replace authentication, authorization, or CSRF protection.
Example
fn handler(request) {
use request <- smol.enable_cross_origin_requests(
request,
using: smol.CrossOriginPolicy(
origins: ["https://app.example.com"],
methods: [http.Get, http.Post],
headers: ["content-type"],
expose: [],
max_age: option.Some(600),
credentials: False,
),
)
// ...
}
pub fn get_interface(server: Server) -> String
Gets the network interface that the server is listening on.
Example
use server <- promise.await(smol.start(builder))
use server <- result.try(server)
let interface = smol.get_interface(server)
io.println("Server listening on interface " <> interface)
pub fn get_port(server: Server) -> Int
Gets the port that the server is listening on.
This is especially useful when using random_port() to determine which
port was assigned.
Example
use server <- promise.await(smol.start(builder))
use server <- result.try(server)
let port = smol.get_port(server)
io.println("Server listening on port " <> int.to_string(port))
pub fn handle_head(
from request: request.Request(ReadableStream),
then next: fn(request.Request(ReadableStream)) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Converts HEAD requests to GET requests, then calls the provided
callback.
This allows a handler for a GET route to also answer HEAD requests with
the same status and headers. The x-original-method header is set to
"HEAD" on the request passed to the callback.
Example
fn handler(request) {
use request <- smol.handle_head(request)
// ...
}
pub fn last_modified(
response: promise.Promise(response.Response(ReadableStream)),
at timestamp: timestamp.Timestamp,
) -> promise.Promise(response.Response(ReadableStream))
Convenience function to set the Last-Modified header on a response from a
Timestamp.
Pairs with the cache middleware, which uses the header to respond
with 304 Not Modified for conditional requests.
Example
smol.send_html(render_post(post))
|> smol.last_modified(at: post.updated_at)
pub fn moved_permanently(
to url: String,
) -> promise.Promise(response.Response(ReadableStream))
Respond with a 308 (Moved Permanently) response.
Example
fn handler(request) {
case request.path_segments(request) {
["api", ..rest] -> handle_api(request)
// move all /backend/* requests to /api/*
["backend", ..rest] -> smol.moved_permanently("/api/" <> string.join(rest, "/"))
}
}
pub fn new(
handler: fn(request.Request(ReadableStream)) -> promise.Promise(
response.Response(ReadableStream),
),
) -> Builder
Creates a new server builder with the given request handler.
The server listens on http://localhost:4000 by default. See the examples
pages for example on how to use this function.
pub fn on_error(
builder: Builder,
on_error: fn(String, dynamic.Dynamic) -> Nil,
) -> Builder
Sets a callback function to be called whenever an error happens.
The function receives a string containing some context as well as the raw javascript error value.
By default, errors are logged to standard error.
Example
fn error_handler(message, error) {
io.println("Errror: " <> message <> ": " <> string.inspect(error))
}
smol.new(handler)
|> smol.error_handler(error_handler)
pub fn port(builder: Builder, port: Int) -> Builder
Sets the port for the server to listen on.
By default smol tries to listen on port 4000.
Example
smol.new(handler)
|> smol.port(8080)
pub fn random_port(builder: Builder) -> Builder
Configures the server to use a random available port.
Useful for testing to avoid port conflicts.
Example
smol.new(handler)
|> smol.random_port()
pub fn read_bytes(
from request: request.Request(ReadableStream),
up_to max_body_size: Int,
then next: fn(BitArray) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Reads the request body as a BitArray and calls the provided callback.
The up_to parameter sets a limit on the size of the body that can be read.
If the body exceeds this limit, a 413 (Payload Too Large) response is sent.
Example
// a simple echo server!
use body <- smol.read_bytes(request, up_to: 1024 * 1024)
smol.send_bytes(body)
pub fn read_bytes_tree(
from request: request.Request(ReadableStream),
up_to max_body_size: Int,
then next: fn(bytes_tree.BytesTree) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Reads the request body as a BytesTree and calls the provided callback.
The up_to parameter sets a limit on the size of the body that can be read.
If the body exceeds this limit, a 413 (Payload Too Large) response is sent.
Example
use body <- smol.read_bytes_tree(request, up_to: 1024 * 1024)
// ... do something with the body ...
smol.send_string("Received bytes")
pub fn read_form(
from request: request.Request(ReadableStream),
up_to max_body_size: Int,
then next: fn(List(#(String, String))) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Reads and decodes a url-encoded request body, then calls the provided callback.
The up_to parameter sets a limit on the size of the body that can be read.
If the body exceeds this limit, a 413 (Payload Too Large) response is sent.
If the body cannot be decoded as UTF-8, a 400 (Bad Request) response is sent.
If the body cannot be parsed as a query string, a 422 (Unprocessable Entity)
response is sent.
Example
fn handler(request) {
use values <- smol.read_form(request, up_to: 1024 * 1024)
let email = list.key_find(values, "email") |> result.unwrap("")
let password = list.key_find(values, "password") |> result.unwrap("")
// ...
}
pub fn read_json(
from request: request.Request(ReadableStream),
using decoder: decode.Decoder(a),
up_to max_body_size: Int,
then next: fn(a) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Reads and decodes a JSON request body, then calls the provided callback.
The decoder parameter is used to decode the JSON into a specific type.
The up_to parameter sets a limit on the size of the body that can be read.
If the body exceeds this limit, a 413 (Payload Too Large) response is sent.
If the body cannot be decoded as UTF-8, a 400 (Bad Request) response is sent.
If the body cannot be parsed as JSON, a 422 (Unprocessable Entity) response is sent.
Example
type User {
User(name: String, age: Int)
}
fn user_decoder() {
use name <- decode.field("name", decode.string)
use age <- decode.field("age", decode.int)
decode.success(User(name:, age:))
}
fn handler(request) {
use user <- smol.read_json(
request,
using: user_decoder(),
up_to: 1024 * 1024,
)
// Process the user
smol.send_string("Hello, " <> user.name)
}
pub fn read_multipart(
from request: request.Request(ReadableStream),
up_to max_body_size: Int,
files_up_to max_file_size: Int,
then next: fn(FormData) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Reads and decodes a multipart form request body, then calls the provided callback.
The up_to parameter sets a limit on the size of the body that can be read.
If the body exceeds this limit, a 413 (Payload Too Large) response is sent.
The files_up_to parameter sets a limit on the size of each uploaded file.
If any file exceeds this limit, a 413 (Payload Too Large) response is sent.
If the multipart form cannot be parsed, a 422 (Unprocessable Entity)
response is sent.
Uploaded files are written to temporary files. They are available while the callback is running and are cleaned up after it completes.
Example
fn handler(request) {
use values <- smol.read_multipart(
request,
up_to: 1024 * 1024,
files_up_to: 512 * 1024,
)
// ...
}
pub fn read_string(
from request: request.Request(ReadableStream),
up_to max_body_size: Int,
then next: fn(String) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Reads the request body as a UTF-8 encoded String and calls the provided callback.
The up_to parameter sets a limit on the size of the body that can be read.
If the body exceeds this limit, a 413 (Payload Too Large) response is sent.
If the body cannot be decoded as UTF-8, a 400 (Bad Request) response is sent.
Example
use body <- smol.read_string(request, up_to: 1024 * 1024)
smol.send_string("You sent: " <> body)
pub fn redirect(
to url: String,
) -> promise.Promise(response.Response(ReadableStream))
Respond with a 303 (See Other) response, redirecting the client GET a different url.
Example
fn handler(request) {
use <- smol.require_method(http.Post)
use user <- smol.read_json(request, up_to: 1024 * 1024, using: user_decoder())
use <- promise.await(update_user_in_database(user))
smol.redirect(to: "/users/" <> int.to_string(user.id) <> "/edit")
}
pub fn require_content_type(
from request: request.Request(ReadableStream),
accept content_types: List(String),
then next: fn() -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
A middleware function ensuring that the provided Content-Type header in the
request matches one of the acceptable values.
Returns a 415 (Unsupported Media Type) response if the header does not match.
Example
fn handler(request) {
use <- smol.require_content_type(request, ["application/json"])
use body <- smol.read_json(request, 1024 * 1024, user_decoder())
// ...
}
pub fn require_method(
from request: request.Request(ReadableStream),
require method: http.Method,
then next: fn() -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
A middleware function ensuring that the request has a specific HTTP method.
Returns a 405 (Method Not Allowed) response if the method does not match.
Example
fn handler(request) {
use <- smol.require_method(request, http.Post)
// ...
}
pub fn rescue(
on_error: fn(dynamic.Dynamic) -> promise.Promise(
response.Response(ReadableStream),
),
inner: fn() -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
A middleware function that allows you to recover from an exception thrown inside the inner request handler.
Example
fn handler(request) {
use <- smol.rescue(fn(_err) { smol.send_status(500) })
// ...
}
pub fn send(
state state: state,
sending sending: output,
) -> promise.Promise(Step(state, output))
A convenience method useful for async functions.
Equivalent to promise.resolve(Send(state:, returning:))
pub fn send_bytes(
bytes: BitArray,
) -> promise.Promise(response.Response(ReadableStream))
Creates a response with binary data.
The response will have content-type application/octet-stream.
Example
fn handler(request) {
let binary_data = <<1, 2, 3, 4, 5>>
smol.send_bytes(binary_data)
}
pub fn send_chunked(
from state: state,
with fun: fn(state) -> promise.Promise(Step(state, BitArray)),
) -> promise.Promise(response.Response(ReadableStream))
Creates a chunked transfer-encoded HTTP response from a yielder function.
The yielder function produces chunks of data over time, allowing efficient streaming of large responses without loading everything into memory at once.
Note that file responses are automatically streamed.
Example
// Stream a large file in chunks
fn stream_handler(request) {
smol.send_chunked(
from: #(0, file_size),
with: fn(state) {
let #(offset, total) = state
case offset >= total {
// we are done!
True -> smol.close()
False -> {
// Read next chunk (10KB at a time)
let chunk_size = int.min(10240, total - offset)
use chunk <- promise.await(read_chunk(path, offset, chunk_size))
// Return chunk and advance to next offset
smol.send(
state: #(offset + chunk_size, total),
sending: chunk,
)
}
}
}
)
}
pub fn send_file(
path: String,
offset offset: Int,
limit limit: option.Option(Int),
or fallback: fn(FileError) -> promise.Promise(
response.Response(ReadableStream),
),
) -> promise.Promise(response.Response(ReadableStream))
Sends a file as the response.
The path parameter is the file path to send. The path will be joined to
the current working directory. Make sure the path cannot escape the folder
you are trying to serve from!
The offset parameter specifies the starting byte offset (default 0).
The limit parameter specifies the maximum number of bytes to send
(default is the entire file).
Content-Length and Content-Type headers will be set automatically based
on the file name and size. ETag and Last-Modified headers will also be
set automatically based on the file size and modification time.
Use the cache middleware to respond with 304 Not Modified
when the request’s validators match the generated headers.
Example
fn handler(request) {
use _error <- smol.send_file(
"./priv/public/index.html",
offset: 0,
limit: option.None,
)
smol.send_status(404)
}
pub fn send_html(
text: String,
) -> promise.Promise(response.Response(ReadableStream))
Creates a html response with the given string.
The response will have context-type: text/html; charset=utf-8.
Example
fn handler(request) {
smol.send_html("<!doctype html><h1>Hello, World!")
}
pub fn send_json(
json: json.Json,
) -> promise.Promise(response.Response(ReadableStream))
Creates a JSON response with the given JSON data.
The response will have Content-Type: application/json; charset=utf-8.
fn handler(request) {
let user = User(name: "Ada", age: 4)
let data = json.object([
#("name", json.string(user.name)),
#("age", json.int(user.age)),
])
smol.send_json(data)
}
pub fn send_status(
status: Int,
) -> promise.Promise(response.Response(ReadableStream))
A small helper to create a response with the given status code and a standard status message.
Example
fn handler(request) {
smol.send_status(404) // Response with "Not Found" body
}
pub fn send_string(
text: String,
) -> promise.Promise(response.Response(ReadableStream))
Creates a plain text response with the given string.
The response will have context-type: text/plain; charset=utf-8.
Example
fn handler(request) {
smol.send_string("Hello, World!")
}
pub fn server_sent_events(
from state: state,
with fun: fn(state) -> promise.Promise(Step(state, SseEvent)),
) -> promise.Promise(response.Response(ReadableStream))
Creates a Server-Sent Events (SSE) response, producing events using an async yielder function.
The unfold function takes a state and returns either:
Send(new_state, event)to emit an event and continue with new stateStep(new_state)to loop once without emitting an eventCloseto end the stream.
smol also provides the convenience functions send, step, and close which return these values already wrapped in a promise.
Example
fn sse_handler(request) {
// Create a counter that generates events every second
use count <- server_sent_events(0)
use _ <- promise.await(promise.wait(1000))
case count < 10 {
True -> {
let event = SseEvent(
event: Some("update"),
data: "Count is " <> int.to_string(count),
id: None,
retry: None,
)
smol.send(state: count + 1, sending: event)
}
False -> smol.close()
}
}
pub fn start(
builder: Builder,
) -> promise.Promise(Result(Server, Nil))
Starts the server with the configured options.
The returned promise resolves once the server has been started or failed to start.
Example
let handler = fn(request) {
smol.send_string("Hello, Joe!")
}
use server <- promise.await(
smol.new(handler)
|> smol.port(8080)
|> smol.start()
)
case server {
Ok(server) -> {
io.println("Server started!")
}
Error(_) -> {
io.println("Could not start the server")
}
}
pub fn status_text(status: Int) -> String
Returns the status text, given a status code.
If the status code is not known, return the code as a string instead.
Example
smol.status_text(418)
// --> "I'm a teapot"
smol.status_text(911)
// --> "911"
pub fn step(
state state: state,
) -> promise.Promise(Step(state, output))
A convenience method useful for async functions.
Equivalent to promise.resolve(Step(state))
pub fn stop(server: Server) -> promise.Promise(Nil)
Stops the server gracefully.
Returns a Promise that resolves when the server has fully stopped.
Example
use server <- promise.await(smol.start(builder))
case server {
Ok(server) -> {
io.println("Server started, stopping in 10 seconds...")
use _ <- promise.await(promise.wait(10_000))
io.println("Stopping the server...")
use _ <- promise.await(smol.stop(server))
io.println("Server stopped.")
}
Error(_) -> {
io.println("Could not start the server!")
}
}
pub fn websocket(
init init: state,
update update: fn(state, msg) -> promise.Promise(
Step(state, WebsocketMessage),
),
on_open handle_open: fn(fn(msg) -> Nil) -> msg,
on_message handle_message: fn(WebsocketMessage) -> msg,
on_close handle_close: msg,
) -> promise.Promise(response.Response(ReadableStream))
Attempt to open a websocket connection in response to this request.
The client has to have made a valid Upgrade request, otherwise smol will
respond with status 426.
When the upgrade succeeds, an actor-like process is started, sending and receiving messages from the client.
type Msg {
ConnectionOpened(dispatch: fn(Msg) -> Nil)
ReceivedMessage(smol.WebsocketMessage)
ConnectionClosed
}
fn websockets_handler(request) {
use count, msg <- smol.websocket(
init: 1,
on_open: ConnectionOpened,
on_message: ReceivedMessage,
on_close: ConnectionClosed
)
case msg {
ConnectionOpened(_) -> smol.step(state)
ConnectionClosed -> smol.close()
ReceivedMessage(smol.Text(text:)) -> {
let response = "Message No. " <> int.to_string(count) <> ": " <> text
smol.send(state: count + 1, sending: smol.Text(response))
}
ReceivedMessage(smol.Binary(_)) -> smol.step(state: count + 1)
}
}
pub fn with_extra_headers(
response: promise.Promise(response.Response(ReadableStream)),
headers: List(#(String, String)),
) -> promise.Promise(response.Response(ReadableStream))
Convenience function to add headers to a response.
Example
smol.send_send_status(201)
|> smol.with_extra_headers([#("Location", "/user/" <> user_id)])
pub fn with_status(
response: promise.Promise(response.Response(ReadableStream)),
status: Int,
) -> promise.Promise(response.Response(ReadableStream))
Convenience function to change the status code of a response.
Example
smol.send_string("I could not find this :(")
|> smol.with_status(404)