Refactoring and additions

This commit is contained in:
Relism
2025-02-27 22:44:18 +01:00
parent 06e6bfa13d
commit 8912f7e738
11 changed files with 152 additions and 26 deletions
+121
View File
@@ -0,0 +1,121 @@
---
banner_title: "Flash - Fullstack Development"
banner_description: "Build fullstack apps with Flash, frontend and backend in one jar."
---
# 🌐 Fullstack Development with Flash
Fullstack development involves building both the **front-end** (user interface) and **back-end** (server logic, database) of a web application. A fullstack developer is responsible for the entire application, ensuring a smooth connection between the two layers.
Popular fullstack frameworks include:
- [Ruby on Rails](https://rubyonrails.org) (Ruby)
- [Django](https://www.djangoproject.com) (Python)
- [Laravel](https://laravel.com) (PHP)
## ⚡ Fullstack Development with Flash
Developing and packaging a fullstack application can be complex, but **Flash** simplifies the process with built-in tools and **quality-of-life features**. With Flash, you can:
- Serve both **frontend** and **backend** from a single application.
- Bundle everything into a **single JAR file** for easy deployment.
- Leverage HDIs for **clean, modular and maintainable route logic**.
![Flash Fullstack Development](../assets/flash-fullstack.png)
## 🚀 Serving Frontend with Flash
Flashs **dynamic file server** enables seamless fullstack development by allowing you to serve frontend assets alongside backend logic. This is made possible by the `RESOURCESTREAM` source type, which serves files from the JARs resources folder.
### ✅ Deployment Workflow
The recommended workflow for packaging a fullstack Flash application:
1. **Compile** the frontend application (React, Vue, Angular, etc.).
2. **Place** the compiled files inside the `resources` folder.
3. **Build** the JAR file with both frontend and backend code.
4. **Deploy** the JAR, serving both frontend and backend seamlessly.
This approach works for any frontend framework that compiles to static files, such as:
- **React** (`npm run build`)
- **Vue.js** (`npm run build`)
- **Angular** (`ng build --prod`)
## ❔ Setting up a Fullstack Flash Application
To get started with fullstack development using Flash, you will first need to choose a frontend framework and set up the build process to compile the frontend assets.
This guide will cover only some of the most popular javascript frontend build tools, but the process is similar for others.
### 🛠️ Setting up the Frontend Build Process
:::warning
When building a frontend application with a javascript framework that will be served from a subdirectory (e.g., `/app`), you need to specify the homepage in the `package.json` file. This ensures that the frontend assets are correctly loaded from the subdirectory.
Using a subdirectory and forgetting to set the homepage will result in broken asset links and a potentially non-functional frontend.
```json
{
"homepage": "/your-subdirectory"
}
```
:::
- `Vite` :
::: details Click to expand
1. Install Vite on your project if it's not already installed or scaffold a new Vite project (follow the [official guide](https://vite.dev/guide/#scaffolding-your-first-vite-project)).
2. Edit the `vite.config.js` or `vite.config.ts` build section to output the compiled files to the `resources/frontend` folder of your Flash project (see example below).
```typescript
// ...
import path from 'path' // [!code ++]
export default defineConfig({
// ...
build: { // [!code ++]
outDir: path.resolve(__dirname, '..path/to/your/src/main/resources/frontend'), // [!code ++]
emptyOutDir: true // [!code ++]
}, // [!code ++]
});
```
3. The output of your Vite build will be placed in the `resources/frontend` folder of your Flash project, ready to be packaged into the JAR file.
:::
- `Parcel`
::: details Click to expand
1. Install Parcel on your project if it's not already installed or scaffold a new Parcel project (follow the [official guide](https://parceljs.org/getting-started/webapp/)).
2. Edit the Parcel build command to output the compiled files to the `resources/frontend` folder of your Flash project (see example below).
```json
{
"scripts": {
"build": "parcel build src/index.html --out-dir ../path/to/your/src/main/resources/frontend"
}
}
```
3. The output of your Parcel build will be placed in the `resources/frontend` folder of your Flash project, ready to be packaged into the JAR file.
:::
### 🛜 Serve the Frontend with Flash
Once you have compiled the frontend assets and placed them in the `resources/frontend` folder, you can serve them using Flash's dynamic file server. This server will serve the frontend assets from the JAR's resources folder.
::: info
NOTE: The following example is assuming you plan to serve the frontend from the root path (`/`).
If you plan to serve the frontend from a subdirectory (e.g., `/app`), you will need to adjust the endpoint path accordingly by adding a trailing `/*`.
:::
```java
public class Example {
public static void main(String[] args) {
FlashServer server = new FlashServer(8080);
server.serveDynamic("/*", new DynamicFileServerConfiguration(
true,
"frontend", // points to the resources/frontend folder
"index.html",
SourceType.RESOURCESTREAM
));
server.start();
}
}
```
### 🚀 Package and Deploy your app!
With the frontend and backend code in place, you can now build the JAR file and deploy it to your server. The JAR file will contain both the frontend and backend code, making it easy to deploy and run your fullstack application.
All you've left to do is run the jarfile on any machine that has Java installed, and your fullstack application will be up and running!
@@ -0,0 +1,142 @@
---
banner_title: "Flash - Handler Default Implementations"
banner_description: "Leverage HDI's for cleaner and more maintainable route logic."
---
# ⚡ Handler Default Implementations (HDI)
HDI's provide an elegant and reliable way to standardize common behaviors across multiple request handlers.
By defining base handlers that extend `RequestHandler` (or even chaining multiple base handlers), you can modularize your logic for aspects like authentication, user data retrieval, and rate limiting.
## 🔗 How It Works
Instead of implementing repeated logic in every handler, you create abstract handler classes that encapsulate common functionality. Your actual route handlers then extend these base classes, inheriting their behavior while focusing purely on request-specific logic.
### 🛠 Example: API Key Authentication
Imagine you want to protect API endpoints with an authentication key by checking it against a database. You can create an abstract `APIKeyProtectedHandler` that extends `RequestHandler` and implements the authentication logic:
```java
public abstract class APIKeyProtectedHandler extends RequestHandler {
protected String apiKey;
public APIKeyProtectedHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
apiKey = req.header("X-API-Key");
if (apiKey == null || !isValidApiKey(apiKey)) {
res.status(403);
res.type("application/json");
return "{\"error\":\"Invalid API Key\"}";
}
return handleAuthorized();
}
// Implement this method in your actual handlers
protected abstract Object handleAuthorized();
private boolean isValidApiKey(String key) {
// Implement key validation logic, e.g., checking against a database
// ...
return true;
}
}
```
Now, your actual API handlers only need to extend APIKeyProtectedHandler, ensuring every request has a valid API key before executing its logic:
```java
@RouteInfo(method = HttpMethod.GET, path = "/data")
public class GetDataHandler extends APIKeyProtectedHandler {
public GetDataHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleAuthorized() {
res.type("application/json");
return "{\"data\":\"Your API response here\"}";
}
}
```
## 🏗️ Chaining HDIs for Modular Logic
HDIs can be chained together to compose multiple layers of behavior. For example,
imagine you want to authenticate users with a token and fetch their data from a database. You can create two abstract handlers that extend one another:
- `ProtectedHandler` ensures authentication.
```java
public abstract class ProtectedHandler extends RequestHandler {
protected String authToken;
public ProtectedHandler(Request req, Response res) {
super(req, res);
}
@Override
public Object handle() {
authToken = req.header("Authorization");
if (authToken == null || !isValidToken(authToken)) {
res.status(401);
res.type("application/json");
return "{\"error\":\"Unauthorized\"}";
}
return handleAuthenticated();
}
protected abstract Object handleAuthenticated();
}
```
- `AuthenticatedHandler` extends ProtectedHandler to fetch user data from the database, and overrides `handleAuthenticated` to ensure the user is authenticated before proceeding.
```java
public abstract class AuthenticatedHandler extends ProtectedHandler {
protected User user;
private String authToken; // inherited from ProtectedHandler
public AuthenticatedHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleAuthenticated() {
user = getUserFromDatabase(authToken);
if (user == null) {
res.status(403);
res.type("application/json");
return "{\"error\":\"User not found\"}";
}
return handleWithUser();
}
protected abstract Object handleWithUser();
}
```
- Your actual implementation handler `UserProfileHandler` extends `AuthenticatedHandler` and implements `handleWithUser` to ensure the user is authenticated and their profile data has been fetched before proceeding.
```java
@RouteInfo(endpoint = "/profile", method = HttpMethod.GET)
public class UserProfileHandler extends AuthenticatedHandler {
// inherited from AuthenticatedHandler
private String username = user.getUsername();
public UserProfileHandler(Request req, Response res) {
super(req, res);
}
@Override
protected Object handleWithUser() {
res.type("application/json");
return "{\"username\":\"" + username + "\"}";
}
}
```