Implemented with SWI-Prolog and its HTTP server libraries.
Consult run.pl
, and then visit the site at localhost:8000.
?- consult('run.pl').
% Started server at http://localhost:8000/
You can use stop(8000).
to shut down the server. This will free the port.
The run.pl
file loads server.pl
and then uses its serve
predicate on port 8000.
% run.pl
% Load the web server.
:- [server].
% Serve it at port 8000.
:- serve(8000).
The server is responsible for initialising each web app and its static files.
% server.pl
% Load web app modules.
:- [app/init].
...
% Predicate to start the server.
serve(Port) :-
http_server(http_dispatch, [port(Port)]).
% Predicate to stop the server.
stop(Port) :-
http_stop_server(Port, []).
Each app is a module that contains its own routes and views.
% app/urls.pl
% Import views.
:- [views].
% Register paths
:- http_handler(root(.), index, []).
The idea is to implement template rendering in the future.
For now, a view just renders plain old HTML code.
% app/views.pl
% Import required HTTP module.
:- use_module(library(http/html_write)).
index(_Request) :-
reply_html_page(
[
title('Prolog Web App'),
link([
id("TailwindCSS"),
rel("stylesheet"),
href("static/tailwind.min.css")
])
],
...
An app contains its own static files. The server registers each app's static files when initialised.
% server.pl
% Load web app modules.
:- [app/init].
% Serve static files for each web app module.
:- http_handler('/static', serve_static, [prefix]).
serve_static(Request) :-
http_reply_from_files('app/static/app', [], Request).
serve_static(Request) :-
http_404([], Request).
Why? Static files have a single purpose, which is to be served as-is. There's no app-specific rendering or templating. Having each app implement its own static file logic would break the DRY principle.