someline/someline-starter
Someline Starter is a Laravel-based application boilerplate for rapid web development. It ships with RESTful API support, user authentication, role and permission management, and a modular structure, giving you a solid foundation to build modern PHP apps faster.
Someline Starter is an application skeleton, not a library you composer require into an existing Laravel app. The fastest path is to clone it, fill in .env, and run the installer/seed commands listed in the README of the tag you're on (older releases ship a someline:* artisan command; if yours doesn't, the manual path is key:generate → jwt:secret → migrate → db:seed).
git clone https://github.com/someline/someline-starter.git my-api
cd my-api && composer install
cp .env.example .env
php artisan key:generate
php artisan jwt:secret # tymon/jwt-auth signs your API tokens
# set DB_*, APP_URL, API_* in .env
php artisan migrate --seed
Look at these five places first, in this order:
.env + config/api.php — API prefix, version, and debug flags decide every URL you'll call.app/Http/Controllers/Api/V1/ — the existing AuthController, UserController, and RoleController are your reference implementations.app/Repositories/ — where query logic lives; BaseRepository subclasses are auto-resolved.app/Transformers/V1/ — Fractal transformers that shape every JSON response.app/Http/Routes — versioned, so a new endpoint means a new route entry, not a controller change.First use case: authenticate and read yourself back. POST {prefix}/{version}/auth/login with email/password returns a JWT; then GET .../auth/me with Authorization: Bearer <token> proves the guard, middleware, repository, and transformer chain all work end to end before you write a line of your own code.
The canonical request flow is Controller → Repository → Transformer → Responder. Keep it that way; the value of this starter is that every resource looks identical.
class PostController extends ApiController
{
public function __construct(PostRepository $repository)
{
$this->repository = $repository;
}
public function index(Request $request)
{
$posts = $this->repository
->with(['author']) // eager load for transformers
->pushCriteria(new PostFilterCriteria($request))
->paginate($request->get('per_page', 15));
return $this->respondWithPagination($posts, new PostTransformer);
}
public function show($id)
{
return $this->respondWithItem(
$this->repository->find($id),
new PostTransformer
);
}
}
Repository conventions. BaseRepository gives you model(), presenter()/transformer(), create, update, delete, findByField, paginate, with, scopeQuery. Reusable filters belong in app/Repositories/Criteria — implement apply($model, $repository) and pushCriteria() them; that's the idiomatic way to keep index endpoints from turning into request-parsing soup.
Transformers, not raw models. Every model returned to a client goes through app/Transformers/V1/*. Use $availableIncludes / $defaultIncludes with includeAuthor() methods for relationship expansion (?include=author,comments), and always eager-load the matching relations in the repository — Fractal will happily fire N+1 queries otherwise.
Auth and RBAC. JWT via tymon/jwt-auth, auth:api guard. Login issues JWTAuth::fromUser($user); logout invalidates the token; me returns Auth::user(). Roles/permissions hang off the User model (hasRole, can) and are enforced with route middleware — put the check on the route, not in the controller body, so it's visible in the route file.
Validation. Form Requests in app/Http/Requests extending the API-aware base request, so failures render as JSON 422 through the same responder envelope instead of redirecting. Never $request->validate() inline in an API controller if you want consistent error shapes.
Versioning. New breaking work means app/Http/Controllers/Api/V2, app/Transformers/V2, and a new route file — copy, don't branch inside V1 classes. This is cheap here and the single biggest reason to keep the structure intact.
Testing. Feature tests hitting the versioned URL with a token are the highest-value tests: $token = JWTAuth::fromUser(factory(User::class)->create()) then $this->getJson($url, ['Authorization' => "Bearer $token"]). Assert on the envelope (data.*), not on the full payload — transformers change often.
composer create-project vs clone. Because it's a full app, require-ing it into another Laravel project will fight over config/, routes, and the App namespace. Start clean or copy the Repositories/Transformers/responder wiring by hand.jwt:secret is mandatory. A missing JWT_SECRET produces confusing "token could not be parsed" errors that look like auth bugs. Also configure a real cache store (Redis) — token blacklisting on logout misbehaves on some cache drivers/expired TTLs.config/api.php version breaks every existing client and every hardcoded test URL at once. Read it from config in tests, not string literals.app/Transformers and the installed someline/* package source over the prose.respondWithItem expects a single model and respondWithCollection/respondWithPagination expect collections — mixing them throws inside Fractal with a vague message. Check config/api.php (or the responder config) for the exact data/error shape before writing SDKs or frontend parsers against it.pushCriteria config, php artisan route:clear && php artisan config:clear. Avoid closures in config arrays if you intend to config:cache.Criteria class for filtering, a new transformer include* method for relations, a repository interface bound in the app's repository service provider for swapping implementations, and overriding the responder's error format to match your organisation's error contract. Those four cover most day-to-day customisation without touching the framework wiring.How can I help you explore Laravel packages today?