Liking cljdoc? Tell your friends :D

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

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 {base-java-version}.

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 individual 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 Java package name.

src/hello.clj
link:example$hello/src/hello.clj[role=include]
1This namespace is called 'hello'. This almost always matches the filename.
2We need to use the io.pedestal.connector namespace, abbreviated as conn.
3We need to use the io.pedestal.http.http-kit namespace, abbreviated as hk.

The clj:ns[] form establishes a name for your namespace; this is the name that other namespaces can use to reference the functions and constants provided by the hello namespace.

The :require clause is how the hello namespace identifies other namespaces it uses; requiring a namespace loads its source and, optionally, creates an alias for the namespace.

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.

Clojure’s main namespace, clojure.core, is special; its contents are mixed into every namespace automatically.

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 [1] 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.

Each namespace is loaded from a Clojure source file. A Clojure namespace provides symbols and those symbols are the names of functions, macros, or constants.

Where the :require says :as something it means we’re making an alias inside our namespace (the hello namespace).

Because hello has required the io.pedestal.connector namespace, it can make use of functions in that namespace, such as io.pedestal.connector/with-default-interceptors. This is the truest form, where every symbol a namespace has a full name that includes the namespace.

That’s cumbersome to type again and again, which is why we set up the alias; conn/with-default-interceptors is exactly the same, but more concise and more readable.

Also, don’t worry about the indentation …​ Clojure ignores whitespace outside 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).

So what exactly are the io.pedestal.connector and io.pedestal.http.http-kit namespaces?

The api:*[ns=io.pedestal.connector] namespace has functions that are used to describe a Pedestal connector.

A Pedestal connector exists to connect 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 TCP/IP port for HTTP requests.

Pedestal has connectors for several popular Java and Clojure HTTP libraries. The api:*[ns=io.pedestal.http.http-kit] namespace adapts Pedestal to use the reference:http-kit.adoc library.

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 a port for incoming HTTP requests.

  2. For each request, decide exactly what operations to execute, including what’s in the response.

  3. Stream the 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
link:example$hello/src/hello.clj[role=include]
1Define a function, using clj:defn[], called greet-handler that takes a single argument, which we will call _request.
2Return 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 ]), 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:

KeyValue

: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 and let Pedestal’s defaults do the work.

There’s absolutely nothing special about this map. It’s a plain old Clojure map - this is typical of 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 reference:response-map.adoc.

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 reference:request-map.adoc 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
link:example$hello/deps.edn[role=include]
1Tell clj where our source code lives.
2Tell 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 reference:http-kit.adoc, 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!), including io.pedestal/pedestal.service and many more [2].

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 [3] in the user namespace.

Now we’re able to enter Clojure code directly and Clojure will evaluate it (turn it into a value) and print the result. 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 clj: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 see that 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 [4]. 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
link:example$hello/src/hello.clj[role=include]

You probably noticed that routes is created with a clj:def[] and not with a clj: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

In a route definition, the first three terms are the URL to match, the HTTP method to match, and the handler for the route. After that are key/value pairs for additional information, such as the name of the route.

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 [5].

We have two additional functions; one to create the Pedestal connector, and a second to create and start the connector.

src/hello.clj
link:example$hello/src/hello.clj[role=include]
1A connector is built from a connector map; this builds an initial map with the specified port number.
2This step adds much basic functionality and security to the connector map.
3This is where routing is added to the connector map.
4Convert the connector map to a connector; the nil is a second parameter of options specific to Http-Kit.
5The start! function starts the connector.

The clj:→[] 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 pattern 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"]
1This 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
link:example$hello/src/hello.clj[role=include]
deps.edn
link:example$hello/deps.edn[role=include]

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. There’s some important distinctions between a Clojure function and a Clojure macro and you don’t have to think about that one bit when you are first getting started with Clojure.
2. Try the command clj -Spath to see them all.
3. 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.
4. We’re simplifying here, we’ll get around to discussing interceptors soon
5. 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.

Can you improve this documentation? These fine people already did:
Howard M. Lewis Ship & Howard Lewis Ship
Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close