slick/module-api
Slick Module API gives Laravel applications a modular foundation for building maintainable APIs. Package endpoints into self-contained modules with their own routes, controllers, resources, and middleware, keeping large APIs organized, versionable, and easy to extend or remove.
slick/module-api remains primarily a contract-only package: it ships interfaces (and small helpers) that a Slick module implements. It still contains no runtime kernel and no bootstrap logic. However, v0.4.0 adds slick/di as a dependency and changes ConsoleModuleInterface::configureConsole() to receive a ContainerInterface, so console-aware modules can build commands through the dependency container.
Minimal setup:
composer require slick/module-api
Steps to get productive in the first 10 minutes:
composer.json for the PHP constraint, PSR-4 prefix, version tag, and the new slick/di dependency. Confirm the exact ContainerInterface FQCN in src/ (likely under Slick\Di\).src/ and read every interface. The primary contract is still the module contract. Also read ConsoleModuleInterface; its configureConsole(ContainerInterface $container) signature is the v0.4.0 change.ConsoleModuleInterface to see how it uses the injected container to construct and configure commands.namespace Acme\Blog;
use Slick\ModuleApi\ModuleInterface; // verify exact FQCN in src/
final class BlogModule implements ModuleInterface
{
// Use the IDE's "Implement methods" — the installed interface is the spec.
}
For a console-aware module:
namespace Acme\Blog;
use Slick\ModuleApi\ConsoleModuleInterface;
use Slick\Di\ContainerInterface; // verify exact FQCN in src/
final class BlogConsoleModule implements ConsoleModuleInterface
{
public function configureConsole(ContainerInterface $container): void
{
// Build/register console commands using the container.
}
}
The first realistic use case is unchanged: you are building a reusable Slick package (router extension, admin panel, persistence layer, console tooling) and want the host application to discover and wire it without editing the framework.
Contract-first dependency direction. Your module package should require slick/module-api and avoid requiring the Slick kernel. If your module implements ConsoleModuleInterface or exposes ContainerInterface in its own public signatures, be aware that slick/module-api now pulls in slick/di; consider requiring slick/di explicitly when your package directly type-hints it.
Module-as-service-provider. The dominant pattern remains: the module acts as a PSR-11 service provider, returning a callable or definition set that receives the container and registers services. Keep the module class thin — wiring only — and put real behavior in ordinary services resolved from the container.
final class BlogModule implements ModuleInterface
{
public function services(): callable
{
return function ($container): void {
$container->set(BlogRepository::class, fn () => new BlogRepository(/* deps */));
};
}
}
Console integration is an optional interface. Implement ConsoleModuleInterface only when your module adds commands. Use configureConsole(ContainerInterface $container) to resolve dependencies and construct commands through the container rather than using globals, static factories, or manual new chains.
final class BlogConsoleModule implements ConsoleModuleInterface
{
public function configureConsole(ContainerInterface $container): void
{
$container->set('blog.command.sync', fn () => new SyncCommand(
$container->get(BlogRepository::class)
));
}
}
One module, one capability. Resist the god module. Split persistence, routing, web, and console concerns into separate modules; the host app composes them. This keeps config keys namespaced and optional interfaces meaningful.
Separate the interface implementation from service wiring. A common refinement is XModule (module identity and config defaults) plus XServiceProvider (service graph registration). It makes unit-testing wiring trivial because you can pass a stub container.
Test against the interface, not the framework. Mock the container and assert your module registers the expected service IDs. For console modules, call configureConsole() with a fake container and assert the expected commands/services are registered. Reserve full-kernel tests for end-to-end module loading.
Config defaults belong in the module. Return defaults from the module and let the host merge them. Namespace keys under your module name to avoid collisions.
Version pinning matters more now. v0.4.0 changes the console configuration signature. Pin slick/module-api to ^0.4 if you rely on the container-aware configureConsole(); keep older pins only if you deliberately support pre-v0.4 modules. Because the package is pre-1.0, treat minor bumps as potentially breaking at interface boundaries.
ConsoleModuleInterface::configureConsole() now receives a ContainerInterface. Existing implementations with the old no-argument signature will fatal. Regenerate method stubs from the installed interface rather than copying old examples.slick/di. The package now depends on it to expose ContainerInterface. If your own module's public signatures reference that interface, declare slick/di directly in your package rather than relying only on transitive resolution.implements the expected interface, that the FQCN in the module list matches, and that the autoloader is current.ConsoleModuleInterface; do not add console methods to the core module contract, forcing every implementation to stub them.slick/di, but that is not license to add your own concrete or third-party dependencies to interfaces others must implement.composer dump-autoload), is it listed in the application's module configuration, does the instance satisfy the expected instanceof check, and — for console modules — did configureConsole() receive and use the container correctly?composer update slick/module-api slick/di and composer dump-autoload after switching to v0.4.0. A stale lockfile or autoload map can leave you with the old interface signature and confusing “interface not found” or fatal signature errors.composer show slick/module-api when diagnosing signature changes.module-api itself will not describe your specific module.How can I help you explore Laravel packages today?