Skip to main content
Version: Next

Responses

A handler that answers in the ordinary way returns a Response. Before looking at the class, it helps to look at what a response becomes on the wire. This handler:

A handler that says hello
Response hello(Request req) => Response.ok(body: Body.fromString('Hello!\n'));

sends these bytes in answer to a request for /hello:

curl -i http://localhost:8080/hello
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
date: Fri, 23 Oct 2026 12:01:43 GMT
content-length: 7

Hello!

A response has three parts, and all three are visible here. The first line is the status line, with the version of HTTP, the status code 200 and its reason phrase, OK. The headers follow, one to a line, up to an empty line. Then comes the body, the seven bytes of Hello! and its newline. Response.ok chose the status code. Body.fromString produced the body, and with it the content-type and content-length headers. Relic added the date header itself. The rest of this page goes through the three parts in that order.

The status code​

Response.ok is one of a set of named constructors, one for each common status code, so that a handler says Response.notFound() rather than Response(404). The error constructors fill in a short body when you give them none:

A response for a missing user
Response findUser(Request req) {
if (req.rawPathParameters[#id] != '1') return Response.notFound();
return Response.ok(body: Body.fromString('User 1\n'));
}
curl -i http://localhost:8080/users/2
HTTP/1.1 404 Not Found
content-type: text/plain; charset=utf-8
date: Fri, 23 Oct 2026 12:01:43 GMT
content-length: 9

Not Found

The constructors cover the codes that most handlers need. ok, noContent and notModified report success. movedPermanently, found and seeOther redirect. badRequest, unauthorized, forbidden, notFound, contentTooLarge, internalServerError and notImplemented report errors. For any other code, call the Response constructor with the number:

A status code without a constructor
Response teapot(Request req) =>
Response(418, body: Body.fromString('I refuse to brew coffee\n'));
HTTP/1.1 418 Status 418
content-type: text/plain; charset=utf-8
date: Fri, 23 Oct 2026 12:01:43 GMT
content-length: 24

I refuse to brew coffee

The reason phrase is Status 418, because the server knows no name for that code. Clients go by the number, so the phrase does no harm.

Redirects​

A redirect is a status code, and a location header that says where to go instead. The redirect constructors take that location:

A permanent redirect
Response oldHome(Request req) => Response.movedPermanently(Uri.parse('/hello'));
HTTP/1.1 301 Moved Permanently
location: /hello
date: Fri, 23 Oct 2026 12:01:44 GMT
content-length: 0

With -L, curl follows the redirect, and prints what /hello answers, Hello!. movedPermanently, a 301, tells clients to use the new address from now on. found, a 302, says that the move is temporary. seeOther, a 303, says to fetch the new address with GET, which suits the answer to a form that was posted.

No content​

Some responses have nothing to say beyond their status. A successful DELETE is a typical case, and Response.noContent() answers it with 204:

An answer without a body
Response deleteUser(Request req) => Response.noContent();
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE http://localhost:8080/users/1
204

A 204 response never has a body. For a 200 with an empty body, give Response.ok a Body.empty(), or no body at all.

The body​

The body can be text, HTML, JSON, raw bytes or a stream. Body.fromString takes a mimeType when the text is not plain text:

HTML and JSON
Response page(Request req) => Response.ok(
body: Body.fromString('<h1>Hello</h1>\n', mimeType: MimeType.html),
);

Response data(Request req) => Response.ok(
body: Body.fromString(
jsonEncode({'name': 'Nova', 'age': 27}),
mimeType: MimeType.json,
),
);

The two handlers answer with these types and bodies:

content-type: text/html; charset=utf-8

<h1>Hello</h1>
content-type: application/json; charset=utf-8

{"name":"Nova","age":27}

The Body decides the content-type and content-length headers. The Body page covers the rest of it, bytes, streams and files, and how Relic detects the type when you give none.

Headers​

Any other header goes in the headers of the response. Headers.build hands you a set of headers to fill in, with typed setters for the common headers and plain names for the rest:

A response with headers of its own
Response cached(Request req) => Response.ok(
headers: Headers.build((h) {
h.cacheControl = CacheControlHeader(maxAge: 3600, publicCache: true);
h['X-Served-By'] = ['relic'];
}),
body: Body.fromString('Keep me for an hour\n'),
);
HTTP/1.1 200 OK
x-served-by: relic
content-type: text/plain; charset=utf-8
cache-control: public, max-age=3600
date: Fri, 23 Oct 2026 12:01:44 GMT
content-length: 20

Keep me for an hour

The typed cacheControl setter took a CacheControlHeader and wrote public, max-age=3600, so a misspelled directive cannot reach the client. Relic writes header names in lowercase, and HTTP treats names the same whatever their case.

Three headers are not yours to set. The Body decides content-type, content-length and the chunked transfer coding, and Relic drops those three when a handler puts them in the headers:

A Content-Type that has no effect
Response mislabeled(Request req) => Response.ok(
headers: Headers.build((h) => h['Content-Type'] = ['application/json']),
body: Body.fromString('Plain text after all\n'),
);
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
date: Fri, 23 Oct 2026 12:01:44 GMT
content-length: 21

Plain text after all

The application/json from the headers is gone. Set the type with mimeType: on the Body, and the length of a stream with contentLength: on Body.fromDataStream. The Headers page has the rest of the headers API, the values a write refuses included.

Answering HEAD​

A HEAD request gets the same status line and headers as GET, and no body. Relic sends a GET route's response to a HEAD request without its body, so a handler rarely has to think about HEAD at all. The Routing page has the details.

Examples & further reading​

Examples​

API documentation​

Further reading​