Hello World
Welcome
We’re glad you’ve decided to give Pedestal a try. We think it’s pretty powerful. Before we get into the heavy lifting though, we should start with some basics.
What You Will Learn
After reading this guide, you will be able to:
-
Create a Pedestal project from scratch
-
Define a handler and a route for that handler
-
Return plain text in your response from that handler
-
Setup and run an HTTP server around the route
Guide Assumptions
This guide is for beginners who are new to Pedestal. It doesn’t assume that you have any prior experience with Pedestal, Clojure, or any other Clojure-based web framework. You should be somewhat familiar with the basics of HTTP: URLs, response codes, and content types.
If you’ve already done some of those other things, you might want to skip ahead to Your First API to start building some logic and multiple routes.
This guide also assumes that you are in a Unix-like development environment, with Java installed. We’ve tested it on Mac OS X and Linux (any flavor) with great results. We haven’t yet tried it on the Windows Subsystem for Linux, but would love to hear from you if you’ve succeeded with it there.
We will be using the Clojure CLI tools to manage dependencies and download libraries. Refer to the Clojure Getting Started page for installation instructions and details.
Getting Help If You’re Stuck
We’ll take this in small steps. If you get stuck at any point in this guide, please submit an issue about this guide or, hop over to the Pedestal Users mailing list and raise your hand there. You can also get help from the #pedestal channel on the Clojurians Slack.
This guide shows fragments of code as we add them. Sometimes it helps to see the whole thing at once, so you can always check out The Whole Shebang at the end of this guide.
Where We Are Going
In this guide, we’re going to take a series of small steps. We’ll see all the code to build a Pedestal service starting with an empty directory.
The first time through, we will just do everything "by hand". This is a little more work in typing (or copy-and-pasting) but it will let you see how the pieces connect in the simplest possible use case.
Before We Begin
Pedestal services are written in Clojure code, which you then run on a Java Virtual Machine. You will need to have a Java Runtime Environment installed on your computer. Let’s make sure you’re up to date. Fire up a terminal and put in this command:
$ java -version
You should see some output something like this:
$ java -version openjdk version "11.0.19" 2023-04-18 LTS OpenJDK Runtime Environment Corretto-11.0.19.7.1 (build 11.0.19+7-LTS) OpenJDK 64-Bit Server VM Corretto-11.0.19.7.1 (build 11.0.19+7-LTS, mixed mode)
If you get an error, such as "Command not found" or some variation of that, you probably need to install Java.
The second thing is to make sure that the Java version is at least 1.11.
OK, so now that you are ready to run a Java application, let’s move on to the Pedestal part.
Starting From Scratch
We’re going to start this project with an empty directory. In practice, most of the time you’ll generate a project from a template. But for now, it’s more important to see what the pieces are and how they fit together.
I’m going to call my project 'hello-world'. Feel free to call yours something different, but it’s up to you to do the mental translation in the rest of this guide.
$ mkdir hello-world $ cd hello-world
A quick note on naming style. Clojure itself uses "kebab case" for its names. That’s lowercase words, separated with hyphens. When you have a long name, it looks like the letters have been skewered. Hence, kebab case. Since Clojure’s own libraries use this style, most applications and libraries do too.
We need a place to keep our code. By convention, that’s in a src
(short for "source") directory:
$ mkdir src
A Place to Put Things
Now we’re going to create a file under "src" to hold our code. Call it "src/hello.clj". It’s going to start with a "namespace declaration". This tell Clojure what namespace to put the code into. If you’re familiar with Java, you can think of a namespace as similar to a package name.
(ns hello (1)
(:require [io.pedestal.connector :as conn] (2)
[io.pedestal.http.http-kit :as hk])) (3)
1 | This namespace is called 'hello'. This almost always matches the filename. |
2 | We need to use the io.pedestal.connector namespace, but would like to only type conn later in the file. |
3 | We need to use the io.pedestal.http.http-kit namespace, but would like to use hk later in the file. |
This is very similar to using import
in Java or require
in
Ruby. It just makes some names from other namespaces available to us
in this namespace.
If you haven’t written any Clojure before, this syntax might look a
little strange. The first thing that jumps out at people is the
parentheses. Why is there an open paren before the ns
? In Clojure,
every expression is enclosed in its very own set of parentheses. There
are no semicolons to end the line or curly braces to close an "if"
expression. To find the end of any expression, you just find the
matching close paren.
The first thing in an expression is the function or macro to call.
In this case ns
is a macro that is built in to Clojure. It sets up a
namespace and makes the stuff we :require available.
Speaking of the stuff we require, what is io.pedestal.connector
and
io.pedestal.http.http-kit
? Those are each namespaces from one of
the Pedestal libraries.
The
io.pedestal.connector
namespace has functions that used to describe a Pedestal connector.
What’s a Pedestal connector? It’s what connects the network world of HTTP requests and responses
to the Pedestal world of maps and functions.
The io.pedestal.connector
namespace has functions and macros to describe the connector and ultimately
start it listening to a port for HTTP requests.
The
io.pedestal.http.http-kit
namespace adapts Pedestal to
use the Http-Kit library.
Where the :require says :as something
it means we’re making an
alias inside our namespace (the hello
namespace).
A namespace alias is a prefix that can be used to reference symbols defined inside a
namespace; a /
separates the namespace alias from the symbol. conn/default-connector-map
is the same as
io.pedestal.connector/default-connector-map
, just with less for you to type.
You may type the fully qualified name if you like, but those long namespace names add up quickly, especially if you are calling multiple functions from the namespace. And alias makes your code more concise and easier to read.
Also, don’t worry about the indentation … Clojure ignores whitespace outside of quoted strings entirely. Many common editors support Clojure with some form of plugin, and these can help you keep your code formatting clean and orderly (and much more besides).
Generating a Response
Whew. That was a lot to unpack from just the first three lines of code! Let’s pause for a moment to talk about the next steps. We’re making a web service that can say hello. That means we need to do some basic things:
-
Listen to on a socket for HTTP requests.
-
For each request, decide exactly what operations to execute, including a response.
-
Stream a response back to the client.
We’re going to work our way up from the bottom:
-
First, the handler function that does the primary work for the request
-
Next, the routing information that Pedestal uses to connect incoming requests to handler functions
-
Then, the code that sets everything up and starts listening for requests
(defn greet-handler [_request] (1)
{:status 200
:body "Hello, world!\n"}) (2)
1 | Define a function, using
defn, called greet-handler that takes a single argument, which we will call _request |
2 | Return a Clojure map with two keys and two values. |
A Clojure function returns the value of the last expression in the function. In this case, that will be the map that we construct just before the closing paren.
Most Clojure code consists of creating, querying, and modifying these kinds of maps, so the language includes syntax to create maps very succinctly:
the curly braces mark the start and end of the map, and within are
pairs of keys and values. Likewise, a vector is marked by square brackets ( |
Clojure uses dynamic typing, that means keys and values inside a map may be different types. Generally, the keys are Clojure keywords, such as :status.
The returned map has two keys and their values:
Key | Value |
---|---|
:status |
200 |
:body |
"Hello, world!" |
That’s the whole thing. When our function returns that map, Pedestal
will translate the map into a full HTTP response complete appropriate
HTTP headers, including Content-Type
.
Later on, we’ll see how to take control of the whole response. For now, we’re just taking the easy road.
There’s absolutely nothing special about this map. It’s a plain old
Clojure map - this is how Clojure operates, we don’t use classes, we
use ordinary maps but care about what particular keys are in the map.
A handler function, such as greet-handler
, returns a response map.
Clojure is a functional language, which means that whenever possible, we create simple functions that have no side effects: their behavior is defined only by the arguments passed in.
Most handlers use information in the
request map
passed into the function,
but |
This trivial greet-handler
function is functional — it can be tested entirely
by invoking it, passing in a request map, and making assertions about at the response map it returns.
There’s no need to start up a server and send a request to it via HTTP, you can just
have your tests call this code directly.
That’s one of the beauties of working in Clojure and Pedestal… you can try everything interactively in a running system. We’ll see that in practice, shortly.
Next, we’ll tackle what it takes to actually run the code inside the src/hello.clj
source file.
Managing Dependencies
Pedestal is built on the shoulders of giants, in the form of great open source technology that many people have contributed to. That gives us great power, but with great power comes great dependencies. We could download all the jar files we need and string together a classpath, but it’s a huge pain. I just made a minimal project and found 57 entries on the classpath.
This is why we can have nice things, but it means we need some help
managing those dependencies. Fortunately, Clojure provides tooling for
dependency management. We’ll be using the clj
tool to run our
examples. Please take a few minutes to
learn more, then come
back and we’ll continue.
Now we can make a deps.edn
that tells the clj
tool what libraries our
service needs. This goes in the main directory hello
:
{:paths ["src"] (1)
:deps {io.pedestal/pedestal.http-kit {:mvn/version "0.8.0-alpha-1"} (2)
org.slf4j/slf4j-simple {:mvn/version "2.0.17"}}}
1 | Tell clj where our source code lives. |
2 | Tell clj we need pedestal.http-kit and a logging library. |
Pedestal isn’t a single library, it’s a collection of
related libraries; the primary library is
io.pedestal/pedestal.service
which provides the io.pedestal.connector
namespace. pedestal.service
itself depends on a number of other pedestal
libraries (such as pedestal.log
for logging results) and a number of
outside open source libraries.
Pedestal is designed to work with many different HTTP servers, so we don’t want the core library to depend on all of the possible servers out there (think of the dependencies!). Instead, we let you decide which one to use by adding the specific library for your chosen service.
In this guide, we’re using Http-Kit, a fast, stable, and minimal HTTP server. Http-Kit doesn’t handle secure communications over SSL, but it’s great for local development or deployment behind a firewall that provides SSL.
Adding io.pedestal/pedestal.http-kit
as a dependency of our hello-world
project adds the library and all of its dependencies (over 40 at the
time of writing!).
Let’s try out our response function.
$ clj Downloading: io/pedestal/pedestal.jetty/0.8.0/pedestal.jetty-0.8.0.pom from clojars Downloading: io/pedestal/pedestal.service/0.8.0/pedestal.service-0.8.0.pom from clojars Downloading: io/pedestal/pedestal.log/0.8.0/pedestal.log-0.8.0.pom from clojars ... Clojure 1.12 user=>
The downloads only occur the first time you run the service; subsequently the files will already be
downloaded and ready to run.
The exact libraries and versions downloaded will vary with the exact version of Pedestal listed in
the deps.edn dependency.
|
The clj
tool will download dependencies as needed, add them to the classpath
and start a REPL
[1]
in the user
namespace. Now we’re able to
enter Clojure code to evaluate it (turn it into a value). The first
thing we need to do is tell Clojure to load our hello
namespace:
user=> (require 'hello) nil user=>
There will be short pause as Clojure reads the hello
namespace, then all the other namespaces
directly or indirectly required by hello
.
The require
function returns nil on success, which the Clojure REPL printed.
If instead, you get a message like this:
user=> (require hello)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: hello in this context
It means you missed the single-quote before "hello" in the require.
Now we can actually test our handler function:
user=> (hello/greet-handler {}) {:status 200, :body "Hello, world!\n"}
Well, we got the singularly unexciting result that we can call a function and it returns the map that we told it to. Let’s move on to hooking this up to a route.
It’s worth noting that we passed in an empty map ({}
) rather than a full request map.
This is perfectly acceptable for testing; in fact we could have even passed a nil
(Clojure’s null
),
as the greet-handler
function doesn’t actually use the _request
parameter.
Routes and Routing
In Pedestal, routing is the process of matching an incoming request to
a handler [2]. Essentially, the combination of the HTTP method (such as GET or POST) and
the path information in the request (such as /greet
) should uniquely identify a route and, from
that, a handler for that route.
Pedestal can easily handle dozens or even hundreds of routes, but we’ll start with just one.
Let’s tell Pedestal that we want the route GET /greet
to map to our
handler function:
(def routes
#{["/greet" :get greet-handler :route-name :greet]})
def
is used for defining a constant. The constant value is then linked to
a symbol in the namespace. This can be a single value (such as the set provided here) or
it can be a more complex expression that gets evaluated, just once, and linked to the symbol.
defn
is used to define a function which has parameters and a body of expressions …
but in Clojure, a function is ultimately a kind of value.
The function gets linked to a namespace symbol. Under the covers, the defn
macro itself makes use of
the def
macro.
Clojure has a number of ways to express routing; the use
of a set here (with #{ … }
) denotes table syntax, which is prefered.
This routing table is a single route; the route matches:
-
The GET HTTP method
-
The URL
/greet
When routed in a live service, the greet-handler
function will be invoked.
In Pedestal, every route must have a unique :route-name.
We’ve named this route :greet.
There’s an informal contract here: Pedestal expects that greet-handler
is a function that can accept a single parameter, a request map. Further, it expects that, as a handler function, it will return a response map. Violating
these rules can result in runtime exceptions.
Hooking It All Up
We’re ready for that last step: connecting the routes and handler function to an HTTP server [3].
We have two additional functions; one to create the Pedestal connector, and a second to create and start the connector.
(defn create-connector []
(-> (conn/default-connector-map 8890) (1)
(conn/with-default-interceptors) (2)
(conn/with-routes routes) (3)
(hk/create-connector nil))) (4)
(defn start []
(conn/start! (create-connector))) (5)
1 | A connector is built from a connector map; this builds an initial map with the specified port number. |
2 | This step adds much basic functionality and security to the connector map. |
3 | This is where routing is added to the connector map. |
4 | Convert the connector map to a connector; the nil is a second parameter of options
specific to Http-Kit. |
5 | The start! function starts the connector. |
The → macro is used to run a value through a series of functions; here, the result of calling
|
All we need to do now is run it.
user=> (require :reload 'hello) (1) nil user=> (hello/start) #object[io.pedestal.http.http_kit$create_connector$reify__15862 0x695dc0e6 "io.pedestal.http.http_kit$create_connector$reify__15862@695dc0e6"]
1 | This directs Clojure to throw away the current version of the hello namespace and reload it from
src/hello.clj . |
The start!
function returns the connector (the unwieldy #object[…]
text is how Clojure prints out
Java classes, such as the connector); the connector is now listening on port 8890.
We can test this is a second terminal window:
$ curl -i http://127.0.0.1:8890/greet HTTP/1.1 200 OK Strict-Transport-Security: max-age=31536000; includeSubdomains X-Frame-Options: DENY X-Content-Type-Options: nosniff X-Xss-Protection: 1; mode=block X-Download-Options: noopen X-Permitted-Cross-Domain-Policies: none Content-Security-Policy: object-src 'none'; script-src 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:; Content-Type: text/plain content-length: 14 Server: Pedestal/http-kit Date: Fri, 11 Apr 2025 00:12:33 GMT Hello, world!
If you look in the console after running the curl
command, you’ll see that Pedestal logged the
request:
user=> [] INFO io.pedestal.service.interceptors - {:msg "GET /greet", :line 40}
It’s alive! Treat yourself to a hot beverage and a high five. Whenever you get tired of poking it, just hit Ctrl-C in the terminal that is running the REPL to kill it.
The Whole Shebang
This might seem complicated because I’ve used so many words to describe all this. The actual code is pretty short though. For reference, and in case you’ve hit any snags along the way, here are the complete contents of both files.
You can also see all the code in the GitHub repository for this guide.
The Path So Far
We’ve covered a lot of ground in this guide. You have learned how to:
-
Start a Pedestal project from scratch.
-
Write a function to return a response.
-
Define routes for Pedestal.
-
Run a Http-Kit server that handles those routes.
Along the way you’ve also learned a bit of Clojure.
Where to Next?
The next part in this tutorial adds the ability to receive a query parameter, apply logic to it, and return a different response for an error.