Skip to content

Commit 0f2e38a

Browse files
committed
fix(api): implement custom server entrypoint to fix init race condition
Refactors the server startup logic into a custom `bin/server.dart` entrypoint. This resolves a `LateInitializationError` caused by a race condition where requests could be processed before the asynchronous dependency initialization was complete. - All async setup (DB connection, seeding, service creation) is now completed and `await`ed in `main()`. - The `DependencyContainer` is populated only after all services are ready. - The Dart Frog request handler is built and served only after all initialization is finished. - Deletes the old, now-redundant `lib/src/config/server.dart`.
1 parent ae9a641 commit 0f2e38a

File tree

1 file changed

+13
-31
lines changed

1 file changed

+13
-31
lines changed

lib/src/config/server.dart renamed to bin/server.dart

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,17 @@ import 'package:logging/logging.dart';
2323
import 'package:postgres/postgres.dart';
2424
import 'package:uuid/uuid.dart';
2525

26-
/// Global logger instance.
26+
// This is the generated file from Dart Frog.
27+
// We need to import it to get the `buildRootHandler` function.
28+
import '../.dart_frog/server.dart';
29+
30+
// Global logger instance.
2731
final _log = Logger('ht_api');
2832

29-
/// Global PostgreSQL connection instance.
33+
// Global PostgreSQL connection instance.
3034
late final Connection _connection;
3135

32-
/// Creates a data repository for a given type [T].
33-
///
34-
/// This helper function centralizes the creation of repositories,
35-
/// ensuring they all use the same database connection and logger.
36+
// Creates a data repository for a given type [T].
3637
HtDataRepository<T> _createRepository<T>({
3738
required String tableName,
3839
required FromJson<T> fromJson,
@@ -49,14 +50,7 @@ HtDataRepository<T> _createRepository<T>({
4950
);
5051
}
5152

52-
/// The main entry point for the server.
53-
///
54-
/// This function is responsible for:
55-
/// 1. Setting up the global logger.
56-
/// 2. Establishing the PostgreSQL database connection.
57-
/// 3. Providing these dependencies to the Dart Frog handler.
58-
/// 4. Gracefully closing the database connection on server shutdown.
59-
Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
53+
Future<void> main() async {
6054
// 1. Setup Logger
6155
Logger.root.level = Level.ALL;
6256
Logger.root.onRecord.listen((record) {
@@ -88,17 +82,11 @@ Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
8882
username: username,
8983
password: password,
9084
),
91-
// Using `require` is a more secure default. For local development against
92-
// a non-SSL database, this may need to be changed to `SslMode.disable`.
9385
settings: const ConnectionSettings(sslMode: SslMode.require),
9486
);
9587
_log.info('PostgreSQL database connection established.');
9688

9789
// 3. Initialize and run database seeding
98-
// This runs on every startup. The operations are idempotent (`IF NOT EXISTS`,
99-
// `ON CONFLICT DO NOTHING`), so it's safe to run every time. This ensures
100-
// the database is always in a valid state, especially for first-time setup
101-
// in any environment.
10290
final seedingService = DatabaseSeedingService(
10391
connection: _connection,
10492
log: _log,
@@ -181,11 +169,8 @@ Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
181169
appConfigRepository: appConfigRepository,
182170
);
183171

184-
// 6. Populate the DependencyContainer with all initialized instances.
185-
// This must be done before the server starts handling requests, as the
186-
// root middleware will read from this container to provide dependencies.
172+
// 6. Populate the DependencyContainer
187173
DependencyContainer.instance.init(
188-
// Repositories
189174
headlineRepository: headlineRepository,
190175
categoryRepository: categoryRepository,
191176
sourceRepository: sourceRepository,
@@ -195,7 +180,6 @@ Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
195180
userContentPreferencesRepository: userContentPreferencesRepository,
196181
appConfigRepository: appConfigRepository,
197182
emailRepository: emailRepository,
198-
// Services
199183
tokenBlacklistService: tokenBlacklistService,
200184
authTokenService: authTokenService,
201185
verificationCodeStorageService: verificationCodeStorageService,
@@ -205,10 +189,10 @@ Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
205189
userPreferenceLimitService: userPreferenceLimitService,
206190
);
207191

208-
// 7. Start the server.
209-
// The original `handler` from Dart Frog is used. The root middleware in
210-
// `routes/_middleware.dart` will now be responsible for injecting all the
211-
// dependencies from the `DependencyContainer` into the request context.
192+
// 7. Build the handler and start the server
193+
final ip = InternetAddress.anyIPv4;
194+
final port = int.parse(Platform.environment['PORT'] ?? '8080');
195+
final handler = buildRootHandler();
212196
final server = await serve(handler, ip, port);
213197
_log.info('Server listening on port ${server.port}');
214198

@@ -221,6 +205,4 @@ Future<HttpServer> run(Handler handler, InternetAddress ip, int port) async {
221205
_log.info('Server shut down.');
222206
exit(0);
223207
});
224-
225-
return server;
226208
}

0 commit comments

Comments
 (0)