A powerful Laravel package for rapid API development with authentication, code generators, and smart middleware.
Streamline your Laravel projects with pre-built authentication components, Google OAuth integration, standardized API responses, and intelligent code generators that follow best practices.
- Overview
- Requirements
- Installation
- Quick Start Guide
- Commands Reference
- Middleware
- API Response Utility
- Usage Examples
- Configuration
- Testing
- Security
- Contributing
- License
Softigital Core is designed to eliminate repetitive boilerplate code and accelerate Laravel API development. Install complete authentication systems with a single command, generate well-structured services and routes instantly, and enjoy auto-configured middleware that just works.
Key Features:
- 🔐 Complete authentication system (register, login, profile)
- 🌐 Google OAuth integration with ID token verification
- 📦 Standardized JSON response formatting
- 🛡️ Smart middleware (auto-applied JSON responses, optional auth)
- 🎨 Code generators for CRUD, routes, and services
- 📂 Automatic API route structure (v1 versioning)
- ⚡ One-command CRUD generation (model, migration, controller, service, requests, resource, routes)
- ⚙️ Fully configurable via published config file
- PHP: 8.2 or higher
- Laravel: 11.0 or higher
- Laravel Sanctum: For token-based authentication
Install via Composer:
composer require softigital-dev/coreThe service provider registers automatically via Laravel's package auto-discovery.
To customize middleware behavior:
php artisan vendor:publish --tag=softigital-configThis creates config/softigital-core.php for middleware configuration.
Set up the foundational API structure:
php artisan softigital:install baseWhat you get:
- routes/v1/api.php structure
- bootstrap/app.php configured for v1 routing
- ApiResponse utility for standardized responses
Add a complete authentication system:
php artisan softigital:install authWhat you get:
- Login endpoint (
POST /api/v1/auth/login) - Register endpoint (
POST /api/v1/auth/register) - Profile endpoint (
GET /api/v1/auth/me) - Complete controllers, services, requests, and routes
Create a full CRUD API in seconds:
# Create complete CRUD structure for a model
php artisan make:crud Post
# Or create individual components:
# Create route file with API resource routes
php artisan make:route posts --controller=PostController --api
# Generate service with repository pattern
php artisan make:service Post --model=Post --repositoryYour API is ready! The middleware automatically handles JSON responses.
# Test the registration endpoint
curl -X POST http://localhost/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{"name":"John","email":"john@example.com","password":"secret123"}'Install pre-built authentication components.
Available Types:
| Type | Description | What Gets Installed |
|---|---|---|
base |
Base API setup | routes/v1 structure, bootstrap/app.php config, ApiResponse utility |
auth |
Basic authentication | AuthController, AuthService, LoginRequest, RegisterRequest, routes |
google-auth |
Google OAuth | GoogleAuthController, GoogleLoginRequest, google config, migration, google/apiclient package |
Options:
--force: Overwrite existing files without prompting--skip-migration: Skip migration publishing and execution (google-auth only)--skip-composer: Skip composer package installation
Examples:
# Install base setup (recommended first step)
php artisan softigital:install base
# Install basic authentication
php artisan softigital:install auth
# Install Google OAuth with force overwrite
php artisan softigital:install google-auth --force
# Install without running migrations
php artisan softigital:install google-auth --skip-migration
# Display help
php artisan softigital:install --helpAfter installing Google auth, configure .env:
GOOGLE_CLIENT_ID=your-client-id-here
GOOGLE_CLIENT_SECRET=your-client-secret-here
GOOGLE_REDIRECT_URI=your-redirect-uri-hereCreate a new route file in routes/v1/ with automatic registration.
Options:
--controller=Name: Specify controller and import it--resource: Generate full resourceful routes (index, create, store, show, edit, update, destroy)--api: Generate API resource routes (excludes create, edit)
Examples:
# Basic route file
php artisan make:route posts
# With controller
php artisan make:route posts --controller=PostController
# API resource routes (recommended for APIs)
php artisan make:route posts --controller=PostController --api
# Full resource routes
php artisan make:route products --controller=ProductController --resourceGenerated file (routes/v1/posts.php):
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::apiResource('posts', PostController::class);The route file is automatically registered in routes/v1/api.php.
Generate service classes in app/Services/ with subdirectory support.
Options:
--model=Name: Inject model dependency--repository: Generate CRUD repository pattern methods
Subdirectory Support:
Use /, \, or . notation for nested directories:
# All these work the same:
php artisan make:service Auth/Login
php artisan make:service Auth\Login
php artisan make:service Auth.LoginExamples:
# Basic service
php artisan make:service Post
# Service with model
php artisan make:service Post --model=Post
# Full repository pattern with CRUD
php artisan make:service Post --model=Post --repository
# Organized in subdirectories
php artisan make:service Auth/Login --model=User --repository
php artisan make:service Blog/Post --repository
php artisan make:service Payment/StripeGenerated structure:
app/Services/
├── PostService.php
├── Auth/
│ ├── LoginService.php
│ └── RegisterService.php
└── Blog/
└── PostService.php
Generate a complete CRUD structure for a model with all necessary files in one command.
What Gets Generated:
- ✅ Model in
app/Models/ - ✅ Migration in
database/migrations/ - ✅ Service in
app/Services/ - ✅ Controller in
app/Http/Controllers/ - ✅ Store Request (POST) in
app/Http/Requests/ - ✅ Update Request (PATCH) in
app/Http/Requests/ - ✅ Resource in
app/Http/Resources/ - ✅ Route file in
routes/v1/
Options:
--api-prefix=v1: API version prefix for routes (default: v1)--force: Overwrite existing files without prompting
Examples:
# Generate CRUD for Post model
php artisan make:crud Post
# Generate with different API version
php artisan make:crud Product --api-prefix=v2
# Overwrite existing files
php artisan make:crud BlogPost --forceGenerated Files for php artisan make:crud Post:
app/
├── Models/Post.php (empty - define your fields)
├── Services/PostService.php (full CRUD methods)
├── Http/
│ ├── Controllers/PostController.php (complete REST API)
│ ├── Requests/
│ │ ├── StorePostRequest.php (empty - add validation rules)
│ │ └── UpdatePostRequest.php (empty - add validation rules)
│ └── Resources/PostResource.php (empty - define response shape)
database/migrations/2025_xx_xx_xxxxxx_create_posts_table.php (empty)
routes/v1/posts.php (RESTful routes with auth:sanctum)
Generated Controller Methods:
index()- GET /posts (list with pagination)show($id)- GET /posts/{id} (single resource)store(StorePostRequest)- POST /posts (create)update(UpdatePostRequest, $id)- PATCH /posts/{id} (update)destroy($id)- DELETE /posts/{id} (delete)
Generated Routes:
Route::middleware('auth:sanctum')->group(function () {
Route::get('posts', [PostController::class, 'index']);
Route::get('posts/{id}', [PostController::class, 'show']);
Route::post('posts', [PostController::class, 'store']);
Route::patch('posts/{id}', [PostController::class, 'update']);
Route::delete('posts/{id}', [PostController::class, 'destroy']);
});Next Steps After Generation:
- Define model properties and relationships in
app/Models/Post.php - Add table columns in the migration file and run
php artisan migrate - Define validation rules in
StorePostRequest.phpandUpdatePostRequest.php - Customize the resource fields in
PostResource.php - Register the route file in
routes/api.php:require __DIR__.'/v1/posts.php';
Controller Features:
- Uses service layer for clean separation of concerns
- Returns proper HTTP status codes (200, 201, 404, 500)
- Automatic pagination with metadata for index endpoint
- Uses Resource for consistent response formatting
- Integrates with ApiResponse utility for standardized responses
Generated service with --repository (app/Services/PostService.php):
<?php
namespace App\Services;
use App\Models\Post;
class PostService
{
public function getAll()
{
return Post::all();
}
public function findById($id)
{
return Post::findOrFail($id);
}
public function create(array $data)
{
return Post::create($data);
}
public function update($id, array $data)
{
$post = $this->findById($id);
$post->update($data);
return $post;
}
public function delete($id)
{
$post = $this->findById($id);
return $post->delete();
}
}The package includes two smart middleware that enhance your API automatically.
Ensures all API requests return JSON (no HTML error pages).
Auto-Applied: Automatically applies to all api/* routes.
Configuration (config/softigital-core.php):
'force_json' => [
'enabled' => true, // Enable/disable middleware
'auto_apply' => true, // Auto-apply to 'api' middleware group
],What it does:
- Sets
Accept: application/jsonheader for all API routes - Prevents HTML error pages in your API
- Ensures consistent JSON responses for validation errors and exceptions
Disable globally:
// config/softigital-core.php
'force_json' => ['enabled' => false],Allows routes to work for both authenticated and guest users.
Alias: auth.optional
Configuration:
'optional_auth' => [
'enabled' => true,
],Usage Example:
Route::get('/posts', [PostController::class, 'index'])
->middleware('auth.optional');In your controller:
public function index(Request $request)
{
$user = $request->user(); // null if guest, User if authenticated
if ($user) {
return ApiResponse::success('Your posts', $user->posts);
}
return ApiResponse::success('Public posts', Post::where('public', true)->get());
}Use Cases:
- Public feeds with personalized content for logged-in users
- Like/favorite features that work without login
- Content that varies based on authentication status
The package includes ApiResponse utility for standardized JSON responses.
use App\Utils\ApiResponse;
// Success (200)
ApiResponse::success('Operation successful', ['key' => 'value']);
// Created (201)
ApiResponse::created('Resource created', $resource);
// Bad Request (400)
ApiResponse::badRequest('Invalid input', ['field' => 'error message']);
// Not Found (404)
ApiResponse::notFound('Resource not found');
// Forbidden (403)
ApiResponse::forbidden('Access denied');
// Validation Error (422)
ApiResponse::validationError('Validation failed', $validator->errors());
// Server Error (500)
ApiResponse::error('Something went wrong');All responses follow this structure:
{
"status": 200,
"message": "Operation successful",
"meta": null,
"data": {
"key": "value"
}
}class PostController extends Controller
{
public function __construct(private PostService $postService) {}
public function index()
{
$posts = $this->postService->getAll();
return ApiResponse::success('Posts retrieved', $posts);
}
public function store(StorePostRequest $request)
{
$post = $this->postService->create($request->validated());
return ApiResponse::created('Post created successfully', $post);
}
public function show($id)
{
$post = $this->postService->findById($id);
if (!$post) {
return ApiResponse::notFound('Post not found');
}
return ApiResponse::success('Post retrieved', $post);
}
}Request:
POST /api/v1/auth/register
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "securepass123"
}Response:
{
"status": 201,
"message": "User registered successfully",
"meta": null,
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"token": "1|abc123xyz..."
}
}Request:
POST /api/v1/auth/login
Content-Type: application/json
{
"email": "john@example.com",
"password": "securepass123"
}Response:
{
"status": 200,
"message": "Login successful",
"meta": null,
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"token": "2|def456uvw..."
}
}Request:
GET /api/v1/auth/me
Authorization: Bearer 2|def456uvw...Response:
{
"status": 200,
"message": "User retrieved successfully",
"meta": null,
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
}
}
}Request:
POST /api/v1/auth/google
Content-Type: application/json
{
"id_token": "google-id-token-from-frontend"
}Response:
{
"status": 200,
"message": "User authenticated successfully",
"meta": null,
"data": {
"token": "3|ghi789rst...",
"user": {
"id": 2,
"name": "Jane Smith",
"email": "jane@gmail.com",
"google_id": "1234567890"
},
"first_time": true
}
}Building a full REST API for posts:
# 1. Create route file with API resource routes
php artisan make:route posts --controller=PostController --api
# 2. Generate service with repository pattern
php artisan make:service Post --model=Post --repository
# 3. Create controller
php artisan make:controller PostController --api
# 4. Create form requests
php artisan make:request StorePostRequest
php artisan make:request UpdatePostRequestWire up the controller:
// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;
use App\Services\PostService;
use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Utils\ApiResponse;
class PostController extends Controller
{
public function __construct(private PostService $postService) {}
public function index()
{
return ApiResponse::success(
'Posts retrieved',
$this->postService->getAll()
);
}
public function store(StorePostRequest $request)
{
$post = $this->postService->create($request->validated());
return ApiResponse::created('Post created', $post);
}
public function show($id)
{
$post = $this->postService->findById($id);
return ApiResponse::success('Post retrieved', $post);
}
public function update(UpdatePostRequest $request, $id)
{
$post = $this->postService->update($id, $request->validated());
return ApiResponse::success('Post updated', $post);
}
public function destroy($id)
{
$this->postService->delete($id);
return ApiResponse::success('Post deleted');
}
}Your routes are ready:
GET /api/v1/posts- List all postsPOST /api/v1/posts- Create postGET /api/v1/posts/{id}- Show postPUT /api/v1/posts/{id}- Update postDELETE /api/v1/posts/{id}- Delete post
The package automatically creates a versioned API structure:
Generated Structure:
routes/
└── v1/
├── api.php # Main route file
├── auth.php # Auth routes (if installed)
└── google.php # Google OAuth routes (if installed)
Your bootstrap/app.php is automatically updated:
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/health',
then: function () {
Route::prefix('api/v1')
->middleware('api')
->group(base_path('routes/v1/api.php'));
}
)
// ... rest of configurationPublish and edit config/softigital-core.php:
return [
'force_json' => [
'enabled' => true,
'auto_apply' => true, // Auto-apply to 'api' middleware group
],
'optional_auth' => [
'enabled' => true,
],
];Add to .env after installing Google authentication:
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:8000/auth/google/callbacknamespace Tests\Feature;
use Tests\TestCase;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
class AuthenticationTest extends TestCase
{
public function test_user_can_register(): void
{
$response = $this->postJson('/api/v1/auth/register', [
'name' => 'Test User',
'email' => 'test@example.com',
'password' => 'password123',
]);
$response->assertStatus(201)
->assertJsonStructure([
'status',
'message',
'data' => ['user', 'token'],
]);
$this->assertDatabaseHas('users', [
'email' => 'test@example.com',
]);
}
public function test_user_can_login(): void
{
$user = User::factory()->create([
'password' => Hash::make('password123'),
]);
$response = $this->postJson('/api/v1/auth/login', [
'email' => $user->email,
'password' => 'password123',
]);
$response->assertStatus(200)
->assertJsonStructure(['data' => ['token', 'user']]);
}
public function test_authenticated_user_can_get_profile(): void
{
$user = User::factory()->create();
$token = $user->createToken('test-token')->plainTextToken;
$response = $this->withToken($token)
->getJson('/api/v1/auth/me');
$response->assertStatus(200)
->assertJson([
'data' => [
'user' => [
'email' => $user->email,
],
],
]);
}
}- Uses Laravel Sanctum for secure token-based authentication
- Tokens stored in
personal_access_tokenstable - Each login creates a new token
- Revoke tokens by deleting from database or calling
$token->delete()
- Passwords hashed with bcrypt
- Minimum 8 characters enforced
- Never logs or displays passwords
- Google ID tokens verified server-side
- Uses official Google API Client library
google_idstored for account linking- Handles edge cases (existing email conflicts)
Best Practices:
// Always verify Google tokens server-side
$client = new Google_Client(['client_id' => config('google.client_id')]);
$payload = $client->verifyIdToken($idToken);Contributions are welcome! Please follow these steps:
- Fork the repository
- Create a feature branch:
git checkout -b feature/amazing-feature - Commit changes:
git commit -m 'Add amazing feature' - Push to branch:
git push origin feature/amazing-feature - Open a Pull Request
git clone https://github.com/softigital-dev/core.git
cd core
composer installThis package is open-sourced software licensed under the MIT license.
- GitHub Issues: https://github.com/softigital-dev/core/issues
- Author: Youssef Ehab - youssefehab.ofice@gmail.com
- Email verification flow
- Password reset functionality
- Two-factor authentication (2FA)
- Additional OAuth providers (Facebook, GitHub)
- Role-based access control (RBAC)
- API rate limiting utilities
Made with ❤️ by Youssef Ehab