Server-rendered Route modules and server-rendered API Routes give you access to a Server.Request.Request argument.
For example, in a server-rendered route,
you could check a session cookie to decide whether to respond by rendering a page
for the logged-in user, or else respond with an HTTP redirect response (see the Server.Response docs).
You can access the incoming HTTP request's:
methodrawUrlrequestTime (as a Time.Posix)There are also some high-level helpers that take the low-level Request data and let you parse it into Elm types:
Note that this data is not available for pre-rendered pages or pre-rendered API Routes, only for server-rendered pages. This is because when a page is pre-rendered, there is no incoming HTTP request to respond to, it is rendered before a user requests the page and then the pre-rendered page is served as a plain file (without running your Route Module).
That's why RouteBuilder.preRender does not have a Server.Request.Request argument.
import BackendTask exposing (BackendTask)
import RouteBuilder exposing (StatelessRoute)
type alias Data =
{}
data : RouteParams -> BackendTask Data
data routeParams =
BackendTask.succeed Data
route : StatelessRoute RouteParams Data ActionData
route =
RouteBuilder.preRender
{ data = data
, head = head
, pages = pages
}
|> RouteBuilder.buildNoState { view = view }
A server-rendered Route Module does have access to a user's incoming HTTP request because it runs every time the page
is loaded. That's why data has a Server.Request.Request argument in server-rendered Route Modules. Since you have an incoming HTTP request for server-rendered routes,
RouteBuilder.serverRender has data : RouteParams -> Request -> BackendTask (Response Data). That means that you
can use the incoming HTTP request data to choose how to respond. For example, you could check for a dark-mode preference
cookie and render a light- or dark-themed page and render a different page.
Get the Time.Posix when the incoming HTTP request was received.
Get a header from the request. The header name is case-insensitive.
Header: Accept-Language: en-US,en;q=0.5
request |> Request.header "Accept-Language"
-- Just "Accept-Language: en-US,en;q=0.5"
The HTTP request method of the incoming request.
Note that Route modules data is run for GET requests, and action is run for other request methods (including POST, PUT, DELETE).
So you don't need to check the method in your Route Module's data function, though you can choose to do so in its action.
Gets the HTTP Method as an uppercase String.
Examples:
Get
|> methodToString
-- "GET"
The Request body, if present (or Nothing if there is no request body).
If the request has a body and its Content-Type matches JSON, then
try running a JSON decoder on the body of the request. Otherwise, return Nothing.
Example:
Body: { "name": "John" }
Headers:
Content-Type: application/json
request |> jsonBody (Json.Decode.field "name" Json.Decode.string)
-- Just (Ok "John")
Body: { "name": "John" }
No Headers
jsonBody (Json.Decode.field "name" Json.Decode.string) request
-- Nothing
No Body
No Headers
jsonBody (Json.Decode.field "name" Json.Decode.string) request
-- Nothing
Takes a Form.Handler.Handler and
parses the raw form data into a Form.Validated value.
This is the standard pattern for dealing with form data in elm-pages. You can share your code for your Form
definitions between your client and server code, using this function to parse the raw form data into a Form.Validated value for the backend,
and Pages.Form to render the Form on the client.
Since we are sharing the Form definition between frontend and backend, we get to re-use the same validation logic so we gain confidence that
the validation errors that the user sees on the client are protected on our backend, and vice versa.
import BackendTask exposing (BackendTask)
import FatalError exposing (FatalError)
import Form
import Server.Request as Request exposing (Request)
import Server.Response as Response exposing (Response)
type Action
= Delete
| CreateOrUpdate Post
formHandlers : Form.Handler.Handler String Action
formHandlers =
deleteForm
|> Form.Handler.init (\() -> Delete)
|> Form.Handler.with CreateOrUpdate createOrUpdateForm
deleteForm : Form.HtmlForm String () input msg
createOrUpdateForm : Form.HtmlForm String Post Post msg
action :
RouteParams
-> Request
-> BackendTask FatalError (Response ActionData ErrorPage)
action routeParams request =
case request |> Server.Request.formData formHandlers of
Nothing ->
BackendTask.fail (FatalError.fromString "Missing form data")
Just ( formResponse, parsedForm ) ->
case parsedForm of
Form.Valid Delete ->
deletePostBySlug routeParams.slug
|> BackendTask.map
(\() -> Route.redirectTo Route.Index)
Form.Valid (CreateOrUpdate post) ->
let
createPost : Bool
createPost =
okForm.slug == "new"
in
createOrUpdatePost post
|> BackendTask.map
(\() ->
Route.redirectTo
(Route.Admin__Slug_ { slug = okForm.slug })
)
Form.Invalid _ invalidForm ->
BackendTask.succeed
(Server.Response.render
{ errors = formResponse }
)
You can handle form submissions as either GET or POST requests. Note that for security reasons, it's important to performing mutations with care from GET requests,
since a GET request can be performed from an outside origin by embedding an image that points to the given URL. So a logout submission should be protected by
using POST to ensure that you can't log users out by embedding an image with a logout URL in it.
If the request has HTTP method GET, the form data will come from the query parameters.
If the request has the HTTP method POST and the Content-Type is application/x-www-form-urlencoded, it will return the
decoded form data from the body of the request.
Otherwise, this Parser will not match.
Note that in server-rendered Route modules, your data function will handle GET requests (and will not receive any POST requests),
while your action will receive POST (and other non-GET) requests.
By default, [Form]'s are rendered with a POST method, and you can configure them to submit GET requests using withGetMethod.
So you will want to handle any Form's rendered using withGetMethod in your Route's data function, or otherwise handle forms in action.
Get the raw key-value pairs from a form submission.
If the request has the HTTP method GET, it will return the query parameters.
If the request has the HTTP method POST and the Content-Type is application/x-www-form-urlencoded, it will return the
decoded form data from the body of the request.
Otherwise, this Parser will not match.
Note that in server-rendered Route modules, your data function will handle GET requests (and will not receive any POST requests),
while your action will receive POST (and other non-GET) requests.
By default, [Form]'s are rendered with a POST method, and you can configure them to submit GET requests using withGetMethod.
So you will want to handle any Form's rendered using withGetMethod in your Route's data function, or otherwise handle forms in action.
The full URL of the incoming HTTP request, including the query params.
Note that the fragment is not included because this is client-only (not sent to the server).
rawUrl request
-- url: http://example.com?coupon=abc
-- parses into: "http://example.com?coupon=abc"
rawUrl request
-- url: https://example.com?coupon=abc&coupon=xyz
-- parses into: "https://example.com?coupon=abc&coupon=xyz"
Get Nothing if the query param with the given name is missing, or Just the value if it is present.
If there are multiple query params with the same name, the first one is returned.
queryParam "coupon"
-- url: http://example.com?coupon=abc
-- parses into: Just "abc"
queryParam "coupon"
-- url: http://example.com?coupon=abc&coupon=xyz
-- parses into: Just "abc"
queryParam "coupon"
-- url: http://example.com
-- parses into: Nothing
See also queryParams, or rawUrl if you need something more low-level.
Gives all query params from the URL.
queryParam "coupon"
-- url: http://example.com?coupon=abc
-- parses into: Dict.fromList [("coupon", ["abc"])]
queryParam "coupon"
-- url: http://example.com?coupon=abc&coupon=xyz
-- parses into: Dict.fromList [("coupon", ["abc", "xyz"])]
True if the content-type header is present AND matches the given argument.
Examples:
Content-Type: application/json; charset=utf-8
request |> matchesContentType "application/json"
-- True
Content-Type: application/json
request |> matchesContentType "application/json"
-- True
Content-Type: application/json
request |> matchesContentType "application/xml"
-- False
Get a cookie from the request. For a more high-level API, see Server.Session.
Get all of the cookies from the incoming HTTP request. For a more high-level API, see Server.Session.
A value that lets you access data from the incoming HTTP request.