ApiRoute's are defined in src/Api.elm
and are a way to generate files either statically pre-rendered at build-time (like RSS feeds, sitemaps, or any text-based file that you output with an Elm function),
or server-rendered at runtime (like a JSON API endpoint, or an RSS feed that gives you fresh data without rebuilding your site).
Your ApiRoute's get access to a BackendTask
so you can pull in HTTP data, etc. Because ApiRoutes don't hydrate into Elm apps (like pages in elm-pages do), you can pull in as much data as you want in
the BackendTask for your ApiRoutes and it won't effect the payload size. Instead, the size of an ApiRoute is just the content you output for that route.
Similar to your elm-pages Route Modules, ApiRoute's can be either server-rendered or pre-rendered. Let's compare the differences between pre-rendered and server-rendered ApiRoutes, and the different use cases they support.
A pre-rendered ApiRoute is just a generated file. For example:
You could even generate a JavaScript file, an Elm file, or any file with a String body! It's really just a way to generate files, which are typically used to serve files to a user or Browser, but you execute them, copy them, etc. The only limit is your imagination! The beauty is that you have a way to 1) pull in type-safe data using BackendTask's, and 2) write those files, and all in pure Elm!
single : ApiRouteBuilder (BackendTask FatalError String) (List String) -> ApiRoute Response
Same as preRender
, but for an ApiRoute that has no dynamic segments. This is just a bit simpler because
since there are no dynamic segments, you don't need to provide a BackendTask with the list of dynamic segments to pre-render because there is only a single possible route.
preRender : (constructor -> BackendTask FatalError (List (List String))) -> ApiRouteBuilder (BackendTask FatalError String) constructor -> ApiRoute Response
Pre-render files for a given route pattern statically at build-time. If you only need to serve a single file, you can use single
instead.
import ApiRoute
import BackendTask
import BackendTask.Http
import Json.Decode as Decode
import Json.Encode as Encode
starsApi : ApiRoute ApiRoute.Response
starsApi =
ApiRoute.succeed
(\user repoName ->
BackendTask.Http.getJson
("https://api.github.com/repos/" ++ user ++ "/" ++ repoName)
(Decode.field "stargazers_count" Decode.int)
|> BackendTask.allowFatal
|> BackendTask.map
(\stars ->
Encode.object
[ ( "repo", Encode.string repoName )
, ( "stars", Encode.int stars )
]
|> Encode.encode 2
)
)
|> ApiRoute.literal "repo"
|> ApiRoute.slash
|> ApiRoute.capture
|> ApiRoute.slash
|> ApiRoute.capture
|> ApiRoute.slash
|> ApiRoute.literal ".json"
|> ApiRoute.preRender
(\route ->
BackendTask.succeed
[ route "dillonkearns" "elm-graphql"
, route "dillonkearns" "elm-pages"
]
)
You can view these files in the dev server at http://localhost:1234/repo/dillonkearns/elm-graphql.json, and when you run elm-pages build
this will result in the following files being generated:
dist/repo/dillonkearns/elm-graphql.json
dist/repo/dillonkearns/elm-pages.json
Note: dist
is the output folder for elm-pages build
, so this will be accessible in your hosted site at /repo/dillonkearns/elm-graphql.json
and /repo/dillonkearns/elm-pages.json
.
You could use server-rendered ApiRoutes to do a lot of similar things, the main difference being that it will be served up through a URL and generated on-demand when that URL is requested. So for example, for an RSS feed or ical calendar feed like in the pre-rendered examples, you could build the same routes, but you would be pulling in the list of posts or calendar events on-demand rather than upfront at build-time. That means you can hit your database and serve up always-up-to-date data.
Not only that, but your server-rendered ApiRoutes have access to the incoming HTTP request payload just like your server-rendered Route Modules do. Just as with server-rendered Route Modules, a server-rendered ApiRoute accesses the incoming HTTP request through a Server.Request.Parser. Consider the use cases that this opens up:
serverRender : ApiRouteBuilder (Server.Request.Request -> BackendTask FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute Response
preRenderWithFallback : (constructor -> BackendTask FatalError (List (List String))) -> ApiRouteBuilder (BackendTask FatalError (Server.Response.Response Basics.Never Basics.Never)) constructor -> ApiRoute Response
You define your ApiRoute's in app/Api.elm
. Here's a simple example:
module Api exposing (routes)
import ApiRoute
import BackendTask exposing (BackendTask)
import FatalError exposing (FatalError)
import Server.Request
routes :
BackendTask FatalError (List Route)
-> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)
-> List (ApiRoute.ApiRoute ApiRoute.Response)
routes getStaticRoutes htmlToString =
[ preRenderedExample
, requestPrinterExample
]
{-| Generates the following files when you
run `elm-pages build`:
- `dist/users/1.json`
- `dist/users/2.json`
- `dist/users/3.json`
When you host it, these static assets will
be served at `/users/1.json`, etc.
-}
preRenderedExample : ApiRoute.ApiRoute ApiRoute.Response
preRenderedExample =
ApiRoute.succeed
(\userId ->
BackendTask.succeed
(Json.Encode.object
[ ( "id", Json.Encode.string userId )
, ( "name", "Data for user " ++ userId |> Json.Encode.string )
]
|> Json.Encode.encode 2
)
)
|> ApiRoute.literal "users"
|> ApiRoute.slash
|> ApiRoute.capture
|> ApiRoute.literal ".json"
|> ApiRoute.preRender
(\route ->
BackendTask.succeed
[ route "1"
, route "2"
, route "3"
]
)
{-| This returns a JSON response that prints information about the incoming
HTTP request. In practice you'd want to do something useful with that data,
and use more of the high-level helpers from the Server.Request API.
-}
requestPrinterExample : ApiRoute ApiRoute.Response
requestPrinterExample =
ApiRoute.succeed
(\pageId revisionId request ->
Encode.object
[ ( "pageId"
, Encode.string pageId
)
, ( "revisionId"
, Encode.string revisionId
)
, ( "body"
, request
|> Server.Request.body
|> Maybe.map Encode.string
|> Maybe.withDefault Encode.null
)
, ( "method"
, request
|> Server.Request.method
|> Server.Request.methodToString
|> Encode.string
)
, ( "cookies"
, request
|> Server.Request.cookies
|> Encode.dict
identity
Encode.string
)
, ( "queryParams"
, request
|> Server.Request.queryParams
|> Encode.dict
identity
(Encode.list Encode.string)
)
]
|> Response.json
|> BackendTask.succeed
)
-- Path: /pages/:pageId/revisions/:revisionId/request-test
|> ApiRoute.literal "pages"
|> ApiRoute.slash
|> ApiRoute.capture
|> ApiRoute.slash
|> ApiRoute.literal "revisions"
|> ApiRoute.slash
|> ApiRoute.capture
|> ApiRoute.slash
|> ApiRoute.literal "request-test"
|> ApiRoute.serverRender
Internal.ApiRoute response
Internal.ApiRouteBuilder a constructor
The intermediary value while building an ApiRoute definition.
Json.Encode.Value
The final value from defining an ApiRoute.
capture : ApiRouteBuilder (String -> a) constructor -> ApiRouteBuilder a (String -> constructor)
Captures a dynamic segment from the route.
literal : String -> ApiRouteBuilder a constructor -> ApiRouteBuilder a constructor
A literal String segment of a route.
slash : ApiRouteBuilder a constructor -> ApiRouteBuilder a constructor
A path separator within the route.
succeed : a -> ApiRouteBuilder a (List String)
Starts the definition of a route with any captured segments.
withGlobalHeadTags : BackendTask FatalError (List Head.Tag) -> ApiRoute response -> ApiRoute response
Include head tags on every page's HTML.
toJson : ApiRoute response -> Json.Encode.Value
Turn the route into a pattern in JSON format. For internal uses.
getBuildTimeRoutes : ApiRoute response -> BackendTask FatalError (List String)
For internal use by generated code. Not so useful in user-land.
getGlobalHeadTagsBackendTask : ApiRoute response -> Maybe (BackendTask FatalError (List Head.Tag))
For internal use.