A Laravel backend
The notes Freddy built so far are his own, on his Mac. His team's notes live in the Laravel app from Freddy the Notetaker. From here on, the desktop app is that app's desktop half: same users, same API, same validation.
The idea
In the guide's words, an Elyra app can be the desktop half of a product whose
web half is Laravel. It signs the user in with a Sanctum token, calls the same
API as the web, and shows Laravel's validation messages in its forms as if they
were its own. Freddy's private notebook from chapters 5 to 7 stays in the local
database; his team's notes come from the server. The feature is
backend:
elyra = { git = "https://github.com/kwhorne/elyra-framework", tag = "v0.16.0",
features = ["database", "backend"] }
App::new().backend(Backend::new("https://notes.example.com"))
The Laravel side
On the web app, Freddy needs Sanctum and three routes in
routes/api.php: one that issues a token, one that revokes it and one
that returns the user. The guide gives them in full:
php artisan install:api # Sanctum; then add HasApiTokens to App\Models\User
Route::post('/sanctum/token', function (Request $request) {
$request->validate(['email' => 'required|email', 'password' => 'required', 'device_name' => 'required']);
$user = User::where('email', $request->email)->first();
if (! $user || ! Hash::check($request->password, $user->password)) {
throw ValidationException::withMessages(['email' => ['The provided credentials are incorrect.']]);
}
return $user->createToken($request->device_name)->plainTextToken;
});
Route::middleware('auth:sanctum')->group(function () {
Route::delete('/sanctum/token', fn (Request $r) => $r->user()->currentAccessToken()->delete());
Route::get('/user', fn (Request $r) => $r->user());
Route::apiResource('notes', NoteController::class);
});
Different paths are fine: token_route, revoke_route and
user_route on Backend. Since 0.16.0 the repository also
has a Composer package, elyracode/elyra-backend, with these routes as
Route::elyraAuth() and the middleware later chapters need; the 0.16.0
changelog says it is not published yet, so this course writes the routes by hand.
Signing in
import { auth, onSignedOut, ValidationError } from "@elyra/runtime";
try {
await auth.signIn(email, password);
} catch (e) {
if (e instanceof ValidationError) errors = e.errors; // { email: ["…"] }
}
$auth; // { signedIn, user, reason }
onSignedOut((reason) => reason === "expired" && toast("Please sign in again"));
Three things make this safe, and they are chapter 1's untrusted page again:
- The token lives in the OS keychain only, never in the webview or a file, and is restored when the app starts. With no keychain, sign-in fails and the token the server just issued is revoked: there is no plain-text fallback.
- The device is named after the machine and the app, so Freddy recognizes it among his tokens on the web.
- A 401 from any request signs the user out, and the page hears
expired.
Calling the API
let backend = ctx.get::<Backend>();
let page: Page<TeamNote> = backend.get("/api/notes").query(&query).json().await?;
let note: TeamNote = backend.get(format!("/api/notes/{id}")).resource().await?;
let created: TeamNote = backend.post("/api/notes").body(&input).resource().await?;
The token goes as Bearer, and every request asks for JSON, which is
what makes Laravel answer a failed validation with a 422 instead of a redirect.
.resource() unwraps an API Resource's data, and
Page<T> reads both of Laravel's paginators. Under it is
Http, Laravel's HTTP facade in Rust: a 30-second timeout by default,
retries for idempotent methods, HTTPS only except to loopback, and a redirect to
another host does not carry the token.
Laravel's answers, as kinds
| Laravel | The page sees |
|---|---|
422 { errors } | a ValidationError, per field, as from a local command |
401, 419 | kind unauthenticated; the token is forgotten |
403 { message } | kind denied, with Laravel's message |
404 | kind not-found |
409, 412 | kind conflict (chapter 13) |
429 + Retry-After | kind too-many-requests, saying when |
5xx | kind server, without the error page |
| no answer | kind offline |
? in a command turns each into the right error, so Freddy's UI can say
"you're offline" instead of showing an error, and a 422 from his Laravel
StoreNoteRequest lands under the field in the desktop form, just as
chapter 6's local validation did.
Live queries over the API
Chapter 7 carries over. Inside a live command a GET depends on its
resource, the path without the query and trailing ids, so
/api/notes?page=2 and /api/notes/12 are both the notes
resource, and a successful write to it re-runs them. That covers what this app
writes. What a colleague changes on the web is chapter 12.
Or generate all of it
rata make:resource TeamNote --backend /api/notes --generate title:string body:text 'pinned:bool?'
That is chapter 6's resource, with the same screens, types, abilities and events,
except that its commands call the API. There is no migration or seeder, because the
table is the server's, and validation is the server's too. rata prints what the
Laravel side needs, and the tests run against an HttpFake. For now the
project needs the database feature as well, because the resource
registry also lists migrations.
The guides are A Laravel backend and HTTP client.
What you learned
Backendcalls a Laravel API with the user's Sanctum token;Httpunder it is Laravel's HTTP facade in Rust.- Three routes on the Laravel side;
elyracode/elyra-backendhas them too, but is not published yet. - The token lives in the keychain only, and a 401 anywhere signs the user out.
- Laravel's answers arrive as kinds, and a 422 lands in the form per field.
make:resource --backendgenerates a resource whose commands call the API.