DEV Community

sugaiketadao
sugaiketadao

Posted on

Client-Side Data Management - I built a lightweight Java framework for Japan's "SI" projects (third attempt in 10 years) #009

Introduction

If you've ever built business web applications, you've probably hit these friction points:

  • Passing large amounts of data between pages — do you really need to round-trip through the server?
  • Server-side sessions are heavy — resource consumption, session sharing across scaled-out nodes, timeout management…
  • Building an auth system from scratch is painful — LDAP integration, JWT issuance and validation, deciding where to store tokens…

This article presents three design approaches that tackle these problems together:

  1. Scoped browser storage — pass data between pages safely, organized by URL scope
  2. JS-variable session management — client-side session that auto-syncs with the server
  3. Transparent JWT authentication — LDAP + JWT with zero authentication code in business logic

These three are tightly coupled. Combined, they produce a state where no auth or data-management code leaks into your business layer.

1. Scoped Browser Storage — Let Directory Structure Be Your Scope

The Problem: Page-to-Page Data Passing Gets Messy

Without a single-page app, every page transition requires a data handoff. Common approaches and their drawbacks:

Approach Problem
URL parameters Explodes in length with many fields. Timestamps and binary data are awkward.
Hidden fields Embedded in HTML, hard to manage. Accidentally receiving stale data is easy. Detail lists are especially painful.
Cookies Size-limited. Sent to the server on every request.
Raw sessionStorage Keys collide easily. No way to tell which page's data is which.

The Solution: URL-Scoped Storage

Rather than using sessionStorage directly, SIcore wraps it with automatic scope detection derived from the URL path.

Scope Purpose Example
Page scope Data valid only within the current page Temporary search conditions
Module scope Shared across pages in the same directory Passing header data to a detail-edit page
System scope Shared across the entire application User preferences
/app/exmodule/head-edit.html    →  module key: "/app/exmodule/"
/app/exmodule/detail-edit.html  →  module key: "/app/exmodule/"  ← same!
Enter fullscreen mode Exit fullscreen mode

Pages in the same directory automatically share data. There's no naming convention to memorize — your directory layout is your scope design.

How It Works in SIcore

SIcore provides the StorageUtil class for this.

Header edit page (head-edit.js):

// Save the whole form to storage, then navigate to the next page
const headObj = PageUtil.getValues();
StorageUtil.setModuleObj('headData', headObj);
HttpUtil.movePage('detail-edit.html');
Enter fullscreen mode Exit fullscreen mode

Detail edit page (detail-edit.js):

// Retrieve from storage and populate the form
const headObj = StorageUtil.getModuleObj('headData');
PageUtil.setValues(headObj);
Enter fullscreen mode Exit fullscreen mode

The caller doesn't need to know which page stored the data. As long as both pages live in the same module directory, data is naturally shared. The actual storage key becomes something like _module@/app/exmodule/headData — the scope and URL path are injected automatically, so different modules never collide.

Benefits

  • No key collisions — scopes are automatically isolated
  • Directory design = data design — split by feature directory and scopes fall into place
  • No URL parameters needed — works cleanly regardless of field count

2. JS-Variable Session Management — Auto-Sync With the Server

Session data is held in JavaScript variables, not in localStorage or sessionStorage. A page reload clears it — and that turns out to be a security feature, not just a limitation.

The Problem: Server-Side Sessions Are Heavy

Traditional server-side sessions (HttpSession, etc.) come with known costs:

  • Consume server memory (OutOfMemoryError is a real concern at scale)
  • Require session sharing mechanisms when scaling horizontally
  • Need timeout management and cleanup logic

The Solution: JS Variables + Automatic Sync

SIcore stores session data in a JS variable and automatically sends and receives it under the reserved _session key on every server call.

// Inside the framework's server-call logic (simplified)

// Before sending the request: attach session data automatically
req['_session'] = sessionData;

// After receiving the response: update session data automatically
sessionData = res['_session'];
Enter fullscreen mode Exit fullscreen mode

On the server side, business code simply reads from and writes to the _session key in the JSON. The server holds no session state — it's entirely client-driven.

When to Use Storage vs. JS-Variable Session

Browser Storage JS-Variable Session
Where stored sessionStorage JS variable
Server sync None Auto-synced on every call
Survives reload Yes No — cleared on reload
Use for Page-to-page data handoff Data shared with the server

If you want to carry data between pages, use storage. If you want data shared with the server, use session. The distinction is clear.

Benefits

  • Stateless server — no session state on the server side; easy horizontal scaling
  • Clean business code — the framework handles sync; business code never thinks about it
  • Clears on reload — JS variables vanish on page refresh; nothing lingers in storage, which is a security advantage

3. Transparent JWT Authentication — Business Code Doesn't Know Auth Exists

Building auth from scratch — LDAP integration, JWT issuance and validation, choosing where to store tokens — is genuinely tedious. SIcore isolates authentication into three layers, hiding it completely from business code.

Layer Responsibility Business code involvement
Sign-in LDAP auth → JWT issuance → store in JS variable Sign-in page only
Token management Hold token in JS variable; attach automatically to every request None
Server validation Validate JWT on every request; return 401 on failure None

Business pages call the server with zero authentication code.

Full Flow

At sign-in:

[Browser] User enters ID and password on the sign-in page
     ↓
[Server] Authenticate via LDAP → issue JWT on success
     ↓  Response JSON: { _session: { token: "hoge..." } }
[Browser] Receive _session.token
     ↓  Store token in JS variable
[Browser] Navigate to the business page
Enter fullscreen mode Exit fullscreen mode

All subsequent business operations (fully automatic):

[Browser] Business page calls callJsonService() — no auth code
     ↓  Framework automatically adds: Authorization: Bearer hoge...
[Server] Validate JWT → success → run business logic → return response
Enter fullscreen mode Exit fullscreen mode

Why Store the Token in a JS Variable?

localStorage and sessionStorage persist the token across page reloads, eliminating the need to re-authenticate. But both are readable by scripts via XSS attacks.

Storing in a JS variable means the user re-authenticates on every reload — but it also eliminates the token theft vector that persisted storage introduces.

In typical business app usage, page reloads are rare during normal operation. And in SI project environments, "closing the browser signs you out" is often the expected behavior — many clients prefer it that way.

Server-Side JWT Validation (Java)

// AbstractHttpHandler.java (excerpt)
private boolean validateJwt(final HttpExchange exchange) {
  final String authHeader = exchange.getRequestHeaders().getFirst("Authorization");
  if (authHeader == null || !authHeader.startsWith("Bearer ")) {
    return false;
  }
  JwtUtil.validateToken(authHeader.substring(7));
  return true;
}
Enter fullscreen mode Exit fullscreen mode

Sign-in service (Java) — LDAP auth → JWT issuance:

// SigninService.java (excerpt)
try {
  (new InitialDirContext(param)).close(); // LDAP authentication
} catch (final NamingException e) {
  io.session().put("token", ValUtil.BLANK);
  io.putMsg(MsgType.ERROR, "es001");
  return;
}
final String token = JwtUtil.createToken(id);
io.session().put("token", token);
Enter fullscreen mode Exit fullscreen mode

Automatic token attachment (JavaScript) — business code is not involved:

// Inside the framework's server-call logic (simplified)
const token = SessionUtil._token;
if (!ValUtil.isBlank(token)) {
  header['Authorization'] = 'Bearer ' + token;
}
Enter fullscreen mode Exit fullscreen mode

Benefits

  • Clean business code — token management, header injection, and 401 handling are all transparent
  • XSS resistance — no persisted token in storage means no storage-based token theft
  • Stateless — JWT-based; no server-side session required

Conclusion

Here's a summary of the three approaches:

Approach Problem Solution Benefit
Scoped storage Messy page-to-page data passing Auto-detect scope from URL path No key collisions; directory = scope
JS-variable session Heavy server-side sessions Client-side JS variable + auto-sync Stateless server; easy scaling
Transparent JWT auth Auth boilerplate is painful Layer separation; hidden from business code Clean code; XSS resistance

All three share the same philosophy: keep business code free from infrastructure concerns. Data management, session sync, and authentication are all handled by the framework layer — so you can stay focused on the actual business logic.

Related Articles

Check out the other articles in this series!

SIcore Framework Links

All implementation code and documentation are available here:


Thank you for reading!
❤ Reactions are appreciated!

Top comments (0)