To-Do list app using React
In this tutorial we will create a To-Do List app using ActiveJ and React. We will explore how to integrate React in an ActiveJ project and how to simply manage routing using the HTTP module. You can find full example sources on GitHub
Here we will consider only the main app class - ApplicationLauncher
. It uses ActiveJ HttpServerLauncher
and AsyncServlet
classes for setting up an embedded application server. With this approach, you can create servers with no XML configurations or third-party dependencies. Moreover, HttpServerLauncher
will automatically take care of launching, running and stopping the application. You'll only need to provide launcher with servlets.
Creating Launcher
ApplicationLauncher
is the main class of the program. Besides launching the application, it also handles routing and most of the corresponding logic. We will use ActiveJ HttpServerLauncher
and extend it:
public final class ApplicationLauncher extends HttpServerLauncher {
@Provides
RecordDAO recordRepo() {
return new RecordImplDAO();
}
@Provides
Executor executor() {
return newSingleThreadExecutor();
}
First, we provided RecordDAO
with the application business logic and Executor
which is needed for our AsyncServlet
.
In order to transform objects to/from JSON we chose DSL-JSON library,
so we provide a DslJson<?>
object that will handle data marshalling.
AsyncServlet
loads static content from /build
directory and takes care of the routing:
@Provides
IStaticLoader staticLoader(Reactor reactor, Executor executor) {
return IStaticLoader.ofClassPath(reactor, executor, "build/");
}
@Provides
JsonCodec<Record> recordJsonCodec() {
return JsonCodecs.ofObject(Record::new,
"title", Record::getTitle, JsonCodecs.ofString(),
"plans", Record::getPlans, JsonCodecs.ofList(
JsonCodecs.ofObject(Plan::new,
"text", Plan::getText, JsonCodecs.ofString(),
"isComplete", Plan::isComplete, JsonCodecs.ofBoolean())
));
}
@Provides
AsyncServlet servlet(Reactor reactor, IStaticLoader staticLoader, RecordDAO recordDAO, JsonCodec<Record> recordJsonCodec) {
JsonCodec<Map<Integer, Record>> mapJsonCodec = JsonCodecs.ofMap(JsonKeyCodec.ofNumberKey(Integer.class), recordJsonCodec);
return RoutingServlet.builder(reactor)
.with("/*", StaticServlet.builder(reactor, staticLoader)
.withIndexHtml()
.build())
Routing in ActiveJ HTTP module resembles Express approach. Method with(@Nullable HttpMethod method, String path, AsyncServlet servlet) of RoutingServlet
's builder
adds routes to the RoutingServlet
:
method
(optional) is one of the HTTP methods (GET
,POST
, etc.)path
is the path on the serverservlet
defines the logic of request processing. If you need to get some data from therequest
while processing you can use:request.getPathParameter(String key)
/request.getQueryParameter(String key)
(see example ofquery parameter usage
to provide the key of the needed parameter and receive back a corresponding Stringrequest.getPostParameters()
to get a Map of all request parameters
Pay attention to the *
in the provided request. It states that whichever path until the next /
is received, it
will be processed by our static servlet, which uploads static content from /build
directory.
Servlet should be able to add a new record, get all available records, delete record by its id and also mark plans of particular record as completed or not, so we provide corresponding routing:
.with(POST, "/add", request -> request.loadBody()
.then($ -> {
ByteBuf body = request.getBody();
try {
byte[] bodyBytes = body.getArray();
Record record = JsonUtils.fromJsonBytes(recordJsonCodec, bodyBytes);
recordDAO.add(record);
return HttpResponse.ok200().toPromise();
} catch (MalformedDataException e) {
return HttpResponse.ofCode(400).toPromise();
}
}))
.with(GET, "/get/all", request -> {
Map<Integer, Record> records = recordDAO.findAll();
return HttpResponse.ok200()
.withJson(JsonUtils.toJson(mapJsonCodec, records))
.toPromise();
})
//[START REGION_4]
.with(GET, "/delete/:recordId", request -> {
int id = parseInt(request.getPathParameter("recordId"));
recordDAO.delete(id);
return HttpResponse.ok200().toPromise();
})
//[END REGION_4]
.with(GET, "/toggle/:recordId/:planId", request -> {
int id = parseInt(request.getPathParameter("recordId"));
int planId = parseInt(request.getPathParameter("planId"));
Record record = recordDAO.find(id);
Plan plan = record.getPlans().get(planId);
plan.toggle();
return HttpResponse.ok200().toPromise();
})
.build();
Pay attention to the requests with :
, for example:
.with(GET, "/delete/:recordId", request -> {
int id = parseInt(request.getPathParameter("recordId"));
recordDAO.delete(id);
return HttpResponse.ok200().toPromise();
})
:
states that the following characters until the next /
is a variable whose keyword, in this case, is recordId
.
Running the application
To run the example clone ActiveJ and import it as a Maven project. Check out branch v6.0-beta2. Before running the example, build the project (Ctrl + F9 for IntelliJ IDEA).
Then, run the following command in activej/examples/tutorials/react-integration-2/front
directory in terminal:
npm i
npm run-script build
Open ApplicationLauncher
and run its main
method. Then open your favourite browser and go to localhost:8080. Try to add and delete some tasks or mark them as completed