An Express inspired web framework for Java 8 (or higher).
import org.jooby.Jooby;
public class App extends Jooby {
{
get("/", (req, res) ->
res.send("Hey Jooby!")
);
}
public static void main(final String[] args) throws Exception {
new App().start();
}
}version: 0.1.0-SNAPSHOT
- Java 8
- Maven 3.x
Just paste this into a terminal:
mvn archetype:generate -B -DgroupId=com.mycompany -DartifactId=my-app -Dversion=1.0-SNAPSHOT \
-DarchetypeArtifactId=jooby-archetype -DarchetypeGroupId=org.jooby \
-DarchetypeVersion=0.1.0-SNAPSHOT
You might want to edit/change:
-
-DgroupId: A Java package's name
-
-DartifactId: A project's name in lower case and without spaces
-
-Dversion: A project's version, like
1.0-SNAPSHOTor1.0.0-SNAPSHOT
Let's try it!:
mvn archetype:generate -B -DgroupId=com.mycompany -DartifactId=my-app -Dversion=1.0-SNAPSHOT \
-DarchetypeArtifactId=jooby-archetype -DarchetypeGroupId=org.jooby \
-DarchetypeVersion=0.1.0-SNAPSHOT
cd my-app
mvn jooby:run
You should see something similar to this at the end of the output:
INFO [2014-10-27 16:40:17,241] Logging initialized @3193ms
INFO [2014-10-27 16:40:17,321] jetty-9.2.3.v20140905
INFO [2014-10-27 16:40:17,340] Started o.e.j.s.h.ContextHandler@604616a9{/,null,AVAILABLE}
INFO [2014-10-27 16:40:17,357] Started ServerConnector@55802087{HTTP/1.1}{0.0.0.0:8090}
INFO [2014-10-27 16:40:17,358] Started @3314ms
INFO [2014-10-27 16:40:17,358]
Routes:
GET /assets/**/* [*/*] [*/*] (static files)
GET / [*/*] [*/*] (anonymous)
Open a browser and type:
http://localhost:8080/
Jooby! is up and running!!!
- getting started
- overview
- routes
- [path patterns](#path patterns)
- [type of routes](#type of routes)
- [router vs filter](#router vs filter)
- [request params](#request params)
- [request headers](#request headers)
A new directory was created: my-app. Now, let's see how it looks like:
/public
/assets/js/index.js
/assets/css/style.css
/images
welcome.html
/config
application.conf
logback.xml
/src/main/java
/com/mycompany/App.java
The public directory contains *.html, *.js, *.css, *.png, ... etc., files.
The config directory contains *.conf, *.properties, *.json, ... etc., files.
The src/main/java contains *.java (of course) files.
NOTE: The three directory are part of the classpath.
NOTE: So this is Maven, Why don't use the default directory layout?
Good question, Java backend developers usually work with frontend developers and they wont have to look deeply in the layout to find the resources they need (src/main/resources or src/main/webapp).
This is a matter of taste and if you find it problematic, you can just use the default directory layout of Maven.
import org.jooby.Jooby;
public class App extends Jooby {
{
assets("/assets/**"); // 1. static files under assets
get("/", html("welcome.html")); // 2. default route
}
public static void main(final String[] args) throws Exception {
new App().start(); // 3. start the application.
}
}It is pretty simple to add/configure a Jooby application. Steps involved are:
-
extends Jooby
-
define some routes
-
call the
startmethod
Just open a console and type:
mvn jooby:run
The plugin will compile the code if necessary and startup the application.
Of course, you can generate the IDE metadata from Maven and/or import as a Maven project on the IDE of your choice. The all you have to do is run the:
App.java
class. After all, it is plain Java with a main method.
Jooby is an Express inspired micro web framework for Java 8 (or higher).
Jooby API mimics (as much as possible) the Express API.
API is short and easy to learn, around 30 classes. The most notable classes are: [Jooby.Module], [Route] and [WebSocket].
Jooby is organized as a set of reusable modules (a.k.a middleware in Express). A module does as minimum as possible and it should NOT make strong/hard decisions for you, or when it does, it must be 100% configurable. For example, a module for the popular Hibernate library should do:
-
create a session factory
-
expose a way to control transactions
-
expose raw Hibernate API
but NOT:
-
wrap/translate Hibernate exceptions
-
wrap Hibernate API or similar
If a module does as minimum as possible, developers have the power! of setup/configure or take real advantage of native library features without noise.
Like in Express routes can be chained/stacked and executed in the same order they are defined.
A route is represent by [Route] and there are two types of handlers: [Router] and [Filter].
A handler is basically the callback executed while a route is the whole thing: verb, path, handler, etc...
{
get("/", (req, res) ->
log.info("first")
);
get("/", (req, res) ->
log.info("second")
);
get("/", (req, res) ->
res.send("last")
);
}A call to:
http://localhost:8080
will print
first
second
and display: last in the browser
Jooby supports Ant-style path patterns:
com/t?st.html - matches com/test.html but also com/tast.html and com/txst.html
com/*.html - matches all .html files in the com directory
com/**/test.html - matches all test.html files underneath the com path
**/* - matches any path at any level
* - matches any path at any level, shorthand for **/*
In addition to Ant-style path pattern, variables pattern are also possible:
/user/{id} - matches /user/* and give you access to the id var
/user/:id - matches /user/* and give you access to the id var
/user/{id:\\d+} - /user/[digits] and give you access to the numeric id var
{
get("/users/:id", (req, res) ->
res.send(req.param("id").stringValue())
);
// or with braces
get("/users/{id}", (req, res) ->
res.send(req.param("id").stringValue())
);
}Routes are classified in 3 groups: 1) inline; 2) external; or 3) Mvc routes.
We will cover inline vs external routes here and Mvc routes are covered later.
Inline routes use lambdas and are useful for quickstart and/or small/simple applications.
{
get("/", (req, res) -> res.send(req.path()));
post("/", (req, res) -> res.send(req.path()));
... etc...
}External routes are declared in a separated class and looks like:
{
get("/", new ExternalRoute());
}
...
public class ExternalRoute implements Router {
public void handle(Request req, Response res) throws Exeption {
res.send(req.path());
}
}Of course this is also possible with Java 8:
{
// static external route
get("/", ExternalRoute::callback);
}
...
public class ExternalRoute {
public static void callback(Request req, Response res) throws Exeption {
res.send(req.path());
}
}Mvc routes are very similar to controllers in Spring or resources in Jersey. They are covered later.
There are two types of handlers [Router] and [Filter]. The difference between them rely in the way they allow/denied the execution of the next route in the chain. The next examples are identical:
{
get("/", (req, res) -> {
log.info("first");
});
get("/", (req, res) -> {
log.info("second");
});
get("/", (req, res) ->
res.send("last")
);
} {
get("/", (req, res, chain) -> {
log.info("first");
chain.next(req, res);
});
get("/", (req, res, chain) -> {
log.info("second");
chain.next(req, res);
});
get("/", (req, res) ->
res.send("last")
);
}A [Router] always call the next route in the chain, while a [Filter] might or mightn't call the next route in chain.
The API for retriving a param is defined by:
req.param("name")
Always returns a Variant.
If the param is missing or type conversion fails, a 400 Bad Request response will be generated.
You can test the presence of param using Variant.isPresent()
Request params are plain string (or bytes). Params convertion is done sing these rules:
-
Be a primitive type/ or primitive wrapper
-
Have a static method named
valueOf,fromString,forName -
Be an Upload.
-
Have a custom Guice Type Converter
-
Be List, Set or SortedSet, where T satisfies 1, 2 or 3 above. The resulting collection is read-only.
-
Be Optional, where T satisfies 1, 2, 3 or 4.
Path params also belong to the requested URI, but they looks like: /user:id or /user/{id}
String id = req.param("id").stringValue();
Query params also belong to the requested URI, but they looks like: /user?id=123
String id = req.param("id").stringValue();
Form params are available when a application/x-www-form-urlencoded or multipart/form-data request is processed.
String id = req.param("id").stringValue();
The same API works for file uploads:
Upload upload = req.param("myfile").to(Upload.class);
And/or body parts:
// multipart request named: jsonObject with a content type of application/json MyObject object = req.param("jsonObject").to(MyObject.class);
Param resolution is done in this order:
-
path
-
query
-
body (form/multipart)
For example:
curl -X POST -d "name=third" http://localhost:8080/user/first?name=second
Give us:
get("/user:name", (req, res) -> {
List<String> name = req.param("name").toList(String.class);
// path
assertEquals("first", name.get(0));
// query
assertEquals("second", name.get(1));
// body
assertEquals("third", name.get(2));
});
And of course this is a valid call (no error):
assertEquals("first", req.param("name").stringValue());
This is supported, but try to avoid this from your APIs because it is hard to read/follow.
Param are required by default, if you need/require an optional param, just use:
Optional optString = req.param("name").toOptional(String.class);
It's identical on how request params work, except that API call is:
String header = req.header("header").stringValue();