launchpad 0.3.0
launchpad: ^0.3.0 copied to clipboard
The missing dependency injection for Serverpod. Request-scoped and server-wide get_it containers for endpoints, streaming methods and future calls.
Launchpad 🚀 #
The missing dependency injection for Serverpod. Let your pod launch with everything onboard!
Launchpad gives every endpoint method, streaming method and future call its own
request-scoped get_it container, and a server
context for dependencies shared across all requests.
Beta: the API may still change before 1.0.
Installation #
dart pub add launchpad get_it
Launchpad works great with injectable, but it is optional.
Getting started #
1. Register request-scoped dependencies #
Set Launchpad.launchpadInit in your server's main function. It is called with a fresh
GetIt container for every request. If you use injectable, pass the generated
getIt.init() function:
// with injectable
Launchpad.launchpadInit = (getIt) => getIt.init();
// or without
Launchpad.launchpadInit = (getIt) {
getIt.registerFactory<HelloUseCase>(() => HelloUseCase());
};
The current Session is always registered in the request container, so your
dependencies can depend on it.
2. Wrap your endpoint methods #
Wrap every endpoint method that returns a Future with Launchpad.launch:
class HelloEndpoint extends Endpoint {
Future<String> hello(Session session, String name) => Launchpad.launch(
session,
(context) async {
final useCase = context.get<HelloUseCase>();
return useCase(name: name);
},
);
}
Need different dependencies for one method? Pass initDependencies to replace
launchpadInit for that call:
Launchpad.launch(
session,
(context) async => context.get<SpecialService>().run(),
initDependencies: (getIt) {
getIt.registerFactory<SpecialService>(() => SpecialService());
},
);
Streaming methods #
Serverpod endpoint methods can return a Stream. Launchpad has two ways to wrap them,
Launchpad.launchStream and Launchpad.launchStreamSync. Both take the same arguments as
Launchpad.launch, including initDependencies.
Launchpad.launchStream: generator handlers #
Use it when the handler is an async* generator. Dependencies are initialized when the
stream is listened to, and the request context stays available for the whole lifetime
of the stream:
class HelloEndpoint extends Endpoint {
Stream<String> countdown(Session session, int from) =>
Launchpad.launchStream(session, (context) async* {
final serverState = context.serverContext.get<ServerState>();
for (var i = from; i > 0; i--) {
yield '$i... (visitors so far: ${serverState.counter})';
await Future.delayed(const Duration(seconds: 1));
}
yield 'Liftoff!';
});
}
Launchpad.launchStreamSync: returning an existing stream #
Use it when the handler already has a stream to return and doesn't need a generator. Dependencies are initialized immediately and the handler's stream is returned as is:
class ChatEndpoint extends Endpoint {
Stream<ChatMessage> messages(Session session, String channel) =>
Launchpad.launchStreamSync(session, (context) {
final chatService = context.get<ChatService>();
return chatService.messages(channel);
});
}
Which one to use #
launchStream |
launchStreamSync |
|
|---|---|---|
| Handler | async* generator (or any stream) |
Plain function returning a stream |
| Dependencies are set up | When the stream is listened to | Immediately, when the method is called |
| Returned stream | Wrapped, always single-subscription | The handler's stream, unchanged |
| Errors in the handler | Delivered as stream errors | Thrown synchronously |
| Cancelling the stream | Cancels the handler's stream | Cancels the handler's stream |
For Serverpod endpoints both behave the same towards the client: Serverpod sends errors thrown while calling a streaming method to the client as stream errors.
Methods that only take Stream parameters but return a Future keep using
Launchpad.launch.
Future calls #
Future call methods receive a regular Session, so wrap them with Launchpad.launch
just like endpoint methods:
class VisitorReportFutureCall extends FutureCall {
Future<void> report(Session session, String reason) => Launchpad.launch(
session,
(context) async {
final serverState = context.serverContext.get<ServerState>();
session.log('[$reason] Visitors so far: ${serverState.counter}');
},
);
}
After running serverpod generate, schedule it as usual (after pod.start()):
await pod.futureCalls
.callWithDelay(const Duration(seconds: 10))
.visitorReport
.report('startup');
Server context #
Share dependencies and their state across all requests through the server context, for example database drivers or connection pools.
First, register your shared dependencies before pod.start():
void run(List<String> args) async {
final pod = Serverpod(args, Protocol(), Endpoints());
Launchpad.initServerContext(pod, (getIt) {
// e.g. getIt.registerSingleton<YourService>(YourServiceImplementation());
getIt.registerSingleton(ServerState());
});
await pod.start();
}
Then access them through context.serverContext:
class HelloEndpoint extends Endpoint {
Future<String> hello(Session session) => Launchpad.launch(
session,
(context) async {
final serverState = context.serverContext.get<ServerState>();
serverState.counter += 1;
return 'Hello from Serverpod! You are visitor number ${serverState.counter}.';
},
);
}
class ServerState {
int counter = 0;
}
Result: the state is shared between requests:
flutter: Server responded: Hello from Serverpod! You are visitor number 17.
flutter: Server responded: Hello from Serverpod! You are visitor number 18.
flutter: Server responded: Hello from Serverpod! You are visitor number 19.
Warning: the server context lives in memory of a single pod. It is not shared between your server instances, so this counter is not a good way to share state in production. Use Redis or the database for state that must be shared across pods.
API reference #
| API | Purpose |
|---|---|
Launchpad.launchpadInit |
Registers request-scoped dependencies for every request |
Launchpad.initServerContext(pod, init) |
Registers server-wide dependencies once at startup |
Launchpad.launch(session, handler, {initDependencies}) |
Wraps endpoint methods returning a Future, and future calls |
Launchpad.launchStream(session, handler, {initDependencies}) |
Wraps streaming methods with an async* handler |
Launchpad.launchStreamSync(session, handler, {initDependencies}) |
Wraps streaming methods that return an existing stream as is |
LaunchpadRequestContext.get<T>() |
Resolves a request-scoped dependency |
LaunchpadRequestContext.serverContext |
Accesses the server-wide dependencies |
LaunchpadServerContext.get<T>() |
Resolves a server-wide dependency |
session.launchpad |
The request-scoped GetIt container of a session |
Example #
A complete Serverpod 4 project (server, client and Flutter app) using endpoints, a
streaming method and a future call lives in the example directory.