{#==========================================
Docs : "Quick Tutorial"
==========================================#}
Here, we'll present a quick tutorial on how to develop
a Spincast application : as a traditional website or as a
Quick Tutorial
SPA application. We won't go into too much details
so, to dig deeper, have a look at :
A "traditional website" is a web application where the server generates
HTML pages, using a Templating Engine.
This is different from the more recent
SPA approach, where the interface is generated
client side (using javascript) and where the backend only provides REST services
(by returning Json or, more rarely, XML).
Bootstrapping a Spincast application involves 3 main steps :
Routes and Route Handlers. We're going to
see those in a minute.
Spincast.configure()
.module(new AppModule())
.init(args);
Please read the whole section dedicated to bootstrapping for more information about this topic.
The quickest way to start a Spincast application is to
download the Quick Start application and to adapt it
to your needs.
You define some Routes and you specify which Route Handlers should handle them. The
Route Handlers are often methods in a controller but can also be defined inline, directly
in the Route definitions.
The Routes definitions can be all grouped together in a dedicated class or can be defined in controllers
directly (have a look at The Router is dynamic for an example).
You can learn more about the various routing options in the Routing
section, but here's a quick example of Route definitions :
// For a GET request. Uses a method reference
// to target a controller method as the Route Handler :
router.GET("/books/${bookId}").handle(bookController::booksGet);
// For any HTTP request. Uses an inine Route Handler :
router.ALL("/hello").handle(context -> context.response().sendPlainText("Hello!"));
Most of the time, a Route Handler is implemented as a method in a controller.
It receives a Request Context object as a parameter.
This Request Context is
extensible and is one
of the most interesting parts of Spincast! In this quick example, we simply use the
default Request Context implementation,
"DefaultRequestContext" :
public class BookController {
// Route Handler dedicated to handle GET requests
// for a book : "/books/${bookId}"
public void booksGet(DefaultRequestContext context) {
// ...
}
}
In your Route Handlers, you use the Request Context object and its various
add-ons to get the information you need about the current request :
public void booksGet(DefaultRequestContext context) {
// Path parameter
// From "/books/${bookId}" for example
String bookId = context.request().getPathParam("bookId");
// QueryString parameter
String page = context.request().getQueryStringParamFirst("page");
// Field received from a POSTed form
String newTitle = context.request().getFormData().getString("newTitle");
// HTTP Header
String authorizationHeader = context.request().getHeaderFirst("Authorization");
// Cookie
String localeCookieValue = context.request().getCookie("locale");
//...
}
You process the current request using any business logic you need, and you build the
model for the response. This response model
is a JsonObject accessible via
"context.response().getModel()" : it is the object
where you store all the information you want to return as the response.
You may add to this response model the variables you want
your templates to have access to :
public void booksGet(DefaultRequestContext context) {
//...
JsonObject book = context.json().create();
book.set("author", "Douglas Adams");
book.set("title", "The Hitchhiker's Guide to the Galaxy");
// Adds the book to the response model
context.response().getModel().set("book", book);
//...
}
When you develop a traditional website, you usually want to render a template
so HTML is going to be displayed.
To do so, you use the integrated Templating Engine :
public void booksGet(DefaultRequestContext context) {
//... builds the response model
// Sends the response model as HTML, using a template
context.response().sendTemplateHtml("/templates/book.html");
}
Here is a template example using the syntax of the default Templating Engine,
Pebble. Notice that the variables
we added to the response model are available.
{% verbatim %}
{% if book is not empty %}
<div class="book">
<h2>{{book.title}}</h2>
<p>Author : {{book.author}}</p>
</div>
{% else %}
<div>
Book not found!
</div>
{% endif %}{% endverbatim %}
The main difference between a SPA application
(or a set of plain REST services) and a
traditional website, is that in a SPA you
don't generate HTML server side.
Instead, most of the logic is client-side, and your Spincast application only acts as a provider
of REST services
to which your client-side application talks using Json
or, more rarely, XML.
Bootstrapping a Spincast application involves 3 main steps :
Routes and Route Handlers. We're going to
see those in a minute.
Spincast.configure()
.module(new AppModule())
.init(args);
Please read the whole section dedicated to bootstrapping for more information about this topic.
The quickest way to start a Spincast application is to
download the Quick Start application and to adapt it
to your needs.
You define some Routes and you specify which Route Handlers should handle them. The
Route Handlers are often methods in a controller but can also be defined inline, directly
on the Route definitions.
The Routes definitions can be all grouped together in a dedicated class or can be defined in controllers
directly (have a look at The Router is dynamic for an example).
SPA, you want to return a single HTML
page : that index page is going to load .js files and, using those, will bootstrap your
client-side application. Using Spincast, you can return that index page as a
Static Resource, or you
can generate it using a template. Let's first see how you could return the
index page as a Static Resource :
// The static "index.html" page that is going to bootstrap
// our SPA
router.file("/").classpath("/index.html").handle();
// The resources (.js, .css, images, etc.) will
// be located under the "/public" path :
router.dir("/public").classpath("/myResources").handle();
// ... the REST endpoints routes
"index.html" file when a "/" request is made.
In this HTML page, you are going to load all the
required resources (mostly .js files first), and
bootstrap your whole application.
You can also use a template to generate the
first index page. This allows you to dynamically tweak it, to
use variables. Here's an example :
// Inline Route Handler that evaluates
// a template to generate the HTML index page.
router.GET("/").handle(context -> {
// Adds some variables to the response model so
// the template has access to them.
context.response().getModel().set("randomQuote", getRandomQuote());
// Renders the template
context.response().sendTemplateHtml("/index.html");
});
// The resources (.js, .css, images, etc.) will
// be located under the "/public" path :
router.dir("/public").classpath("/public").handle();
// ... the REST endpoints routes
Route Handler, instead of
defining it inline like in our example!
Once the Route for the index page and those for the resources are
in place, you add the ones required for your REST endpoints.
For example :
// Endpoint to get a book
router.GET("/books/${bookId}").handle(bookController::booksGet);
// Endpoint to modify a book
router.POST("/books/${bookId}").handle(bookController::booksPost);
// ...
Most of the time, a Route Handler is implemented as a method in a controller.
It receives a Request Context object as a parameter.
This Request Context is
extensible and is one
of the most interesting parts of Spincast! In this quick example, we simply use the
default Request Context implementation,
"DefaultRequestContext" :
public class BookController {
// Route Handler dedicated to handle GET requests
// for a book : "/books/${bookId}"
public void booksGet(DefaultRequestContext context) {
// ...
}
}
In your Route Handlers, you use the Request Context object and its various
add-ons to get the information you need about the
current request (an AJAX request for example) :
public void booksGet(DefaultRequestContext context) {
// The Json body of the request as a JsonObject
JsonObject jsonObj = context.request().getJsonBody();
// Path parameter
// From "/books/${bookId}" for example
String bookId = context.request().getPathParam("bookId");
// HTTP Header
String authorizationHeader = context.request().getHeaderFirst("Authorization");
// Cookie
String localeCookieValue = context.request().getCookie("locale");
//...
}
Very often in a SPA application, or when you develop plain
REST services, you are going to receive a Json object
as the body of a request (with a "application/json" content-type).
In the previous code snippet, context.request().getJsonBody() gets
that Json from the request and creates a JsonObject
from it so it is easy to manipulate.
When you receive a request, you process it using any required business logic, and you then build the
Json (or XML) object to return as a response. There are two ways to achieve that.
The prefered approach, is to create a typed object,
a book created from a Book class for example, and explicitly
send this entity as Json. For example :
public void booksGet(DefaultRequestContext context) {
String bookId = context.request().getPathParam("bookId");
Book someBook = getBook(bookId);
context.response().sendJson(someBook);
}
The second option, probably more useful for
traditional websites though, is to
use the response model to dynamically create the Json
object to send.
You get the response model as a JsonObject
by calling the context.response().getModel() method, you
add elements to it and you send it as Json :
public void booksGet(DefaultRequestContext context) {
// Gets the response model
JsonObject responseModel = context.response().getModel();
// Gets a book
String bookId = context.request().getPathParam("bookId");
Book someBook = getBook(bookId);
// Adds the book to the response model, using
// the "data.book" key
responseModel.set("data.book", book);
// Adds a "code" element to the response model
responseModel.set("code", AppCode.APP_CODE_ACCEPTED);
// Adds a timestamp to the response model
responseModel.set("timestamp", new Date());
// This is going to send the response model as Json
context.response().sendJson();
}
Json response
would have a "application/json" content-type and
would look like this :
{
"code" : 12345,
"timestamp" : "2016-11-06T22:58+0000",
"data" : {
"book" : {
"author" : "Douglas Adams",
"title" : "The Hitchhiker's Guide to the Galaxy"
}
}
}
You consume the Json response from your client-side SPA
application whatever it is built with : Angular,
React, Vue.js,
Ember, etc. Of course, we won't go into details here since
there are so many client-side frameworks!
A Json response can also be consumed by a client
which is not a SPA : it can be a response for a Ajax request
made using Jquery
or plain javascript. Such Json response can also be consumed by a
backend application able to send HTTP requests.