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.

src/hello.clj
(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:

  1. Listen to on a socket for HTTP requests.

  2. For each request, decide exactly what operations to execute, including a response.

  3. 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

src/hello.clj
(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 ([ and ]) and a general list by ( and ) and a set by #{ and }.

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 greet-handler is so trivial that it ignores the request; the _ prefix on the parameter name, _request, is a Clojure convention for parameters that are present but purposely ignored.

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:

deps.edn
{: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:

src/hello.clj
(def routes
  #{["/greet" :get greet-handler :route-name :greet]})

You probably noticed that routes is created with a def and not with a defn.

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.

src/hello.clj
(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 default-connector-map is passed to with-default-interceptors; the result of that is passed to with-routes, and so forth. This is very useful, concise, and common in Clojure code.

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.

src/hello.clj
(ns hello                                                   (1)
  (:require [io.pedestal.connector :as conn]                (2)
            [io.pedestal.http.http-kit :as hk]))            (3)

(defn greet-handler [_request]                              (1)
  {:status 200
   :body   "Hello, world!\n"})                              (2)


(def routes
  #{["/greet" :get greet-handler :route-name :greet]})

(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)
deps.edn
{: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"}}}

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.


1. Read/Eval/Print/Loop. In Clojure, you enter expressions at the prompt, and those are parsed, executed and the result printed back to the console. Anything you can do in a Clojure source file can also be done right at the prompt, including redefining existing functions. REPL-oriented development is powerfully productive, especially when combined with an IDE that can help you interact with your running REPL.
2. We’re simplifying here, we’ll get around to discussing interceptors soon
3. Pedestal calls this a "network connector" as it is the piece that handles HTTP networking. The network connector is an internal component of the Pedestal connector.