├── README.md ├── database ├── migrations │ ├── .gitkeep │ └── 2020_02_28_164547_create_users_table.php ├── seeds │ └── DatabaseSeeder.php └── factories │ └── ModelFactory.php ├── resources └── views │ └── .gitkeep ├── app ├── Console │ ├── Commands │ │ └── .gitkeep │ └── Kernel.php ├── Events │ ├── Event.php │ ├── ExampleEvent.php │ └── UserLoggedIn.php ├── Jobs │ ├── ExampleJob.php │ └── Job.php ├── Http │ ├── Middleware │ │ ├── ExampleMiddleware.php │ │ ├── CheckAge.php │ │ └── Authenticate.php │ └── Controllers │ │ ├── AuthorsController.php │ │ ├── BooksController.php │ │ └── UsersController.php ├── Providers │ ├── EventServiceProvider.php │ ├── AppServiceProvider.php │ └── AuthServiceProvider.php ├── Listeners │ ├── ExampleListener.php │ └── SendIpAddress.php ├── Users.php ├── Traits │ ├── ConsumeExternalService.php │ └── ApiResponder.php ├── Services │ ├── AuthorService.php │ └── BookService.php └── Exceptions │ └── Handler.php ├── storage ├── app │ └── .gitignore ├── logs │ └── .gitignore └── framework │ ├── cache │ └── .gitignore │ └── views │ └── .gitignore ├── file.png ├── .gitignore ├── .editorconfig ├── config ├── services.php ├── auth.php └── mail.php ├── tests ├── TestCase.php └── ExampleTest.php ├── .env.example ├── public ├── .htaccess └── index.php ├── phpunit.xml ├── artisan ├── composer.json ├── routes └── web.php ├── bootstrap └── app.php ├── README.adoc └── Gateway.postman_collection.json /README.md: -------------------------------------------------------------------------------- 1 | ApiGateWay 2 | -------------------------------------------------------------------------------- /database/migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/views/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/Console/Commands/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ahmedalaahagag/api-gateway-php/HEAD/file.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /.idea 3 | Homestead.json 4 | Homestead.yaml 5 | .env 6 | storage/*.key 7 | -------------------------------------------------------------------------------- /app/Events/Event.php: -------------------------------------------------------------------------------- 1 | [ 5 | 'base_uri' => env('AUTHORS_SERVICE_BASE_URI'), 6 | 'secret' => env('AUTHORS_SERVICE_SECRET'), 7 | ], 8 | 'books' => [ 9 | 'base_uri' => env('BOOKS_SERVICE_BASE_URI'), 10 | 'secret' => env('BOOKS_SERVICE_SECRET') 11 | ] 12 | ]; 13 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | request = $request; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/Jobs/ExampleJob.php: -------------------------------------------------------------------------------- 1 | [ 13 | 'App\Listeners\SendIpAddress', 14 | ], 15 | ]; 16 | } 17 | -------------------------------------------------------------------------------- /tests/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 16 | 17 | $this->assertEquals( 18 | $this->app->version(), $this->response->getContent() 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton('mailer', function ($app) { 17 | $app->configure('services'); 18 | return $app->loadComponent('mail', 'Illuminate\Mail\MailServiceProvider', 'mailer'); 19 | }); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/CheckAge.php: -------------------------------------------------------------------------------- 1 | input('age') >= 80) { 17 | return 'You do not satisfy middleware security check and hence cannot proceed'; 18 | } 19 | return $next($request); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Listeners/ExampleListener.php: -------------------------------------------------------------------------------- 1 | $this->baseUri 13 | ]); 14 | if(isset($this->secret)) { 15 | $headers['Authorization'] = $this->secret; 16 | } 17 | $response = $client->request($method, $requestUrl, [ 18 | 'form_params' => $formParams, 19 | 'headers' => $headers 20 | ]); 21 | return $response->getBody()->getContents(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME= 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | APP_TIMEZONE=UTC 7 | 8 | LOG_CHANNEL=stack 9 | LOG_SLACK_WEBHOOK_URL= 10 | 11 | DB_CONNECTION=mysql 12 | DB_HOST=mysql 13 | DB_PORT=3306 14 | DB_DATABASE= 15 | DB_USERNAME= 16 | DB_PASSWORD= 17 | 18 | CACHE_DRIVER=file 19 | QUEUE_CONNECTION=sync 20 | 21 | MAIL_DRIVER=smtp 22 | MAIL_HOST=smtp.mailtrap.io 23 | MAIL_PORT=2525 24 | MAIL_USERNAME= 25 | MAIL_PASSWORD= 26 | MAIL_ENCRYPTION=tls 27 | MAIL_FROM_ADDRESS=hello@example.com 28 | MAIL_FROM_NAME="Exampleapp" 29 | 30 | MICRO_SERVICE_BASE_URI= 31 | MICRO_SERVICE_SECRET= 32 | MICRO_SERVICE_BASE_URI_2= 33 | MICRO_SERVICE_SECRET_2= 34 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /database/factories/ModelFactory.php: -------------------------------------------------------------------------------- 1 | define(App\Author::class, function (Faker\Generator $faker) { 14 | return [ 15 | 'gender' => $gender = $faker->randomElement(['male','female']), 16 | 'name' => $faker->name($gender), 17 | 'country' => $faker->country, 18 | ]; 19 | }); 20 | -------------------------------------------------------------------------------- /app/Jobs/Job.php: -------------------------------------------------------------------------------- 1 | to([$event->request->email]); $msg->from(['blog@blog.com']); }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/migrations/2020_02_28_164547_create_users_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->string('password'); 21 | $table->rememberToken(); 22 | $table->timestamps(); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('users'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests 14 | 15 | 16 | 17 | 18 | ./app 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->router); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Traits/ApiResponder.php: -------------------------------------------------------------------------------- 1 | json($data, $code)->header('Content-Type', 'application/json'); 14 | } 15 | 16 | public function validResponse($data, $code = Response::HTTP_OK) :\Illuminate\Http\JsonResponse 17 | { 18 | return \response()->json(['data' => $data], $code)->header('Content-Type', 'application/json'); 19 | } 20 | 21 | public function errorResponse($message, $code) :\Illuminate\Http\JsonResponse 22 | { 23 | return \response()->json(['message' => $message], $code); 24 | } 25 | 26 | public function errorMessage($message, $code) :\Illuminate\Http\JsonResponse 27 | { 28 | return \response()->json($message, $code)->header('Content-Type', 'application/json'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | run(); 28 | -------------------------------------------------------------------------------- /app/Services/AuthorService.php: -------------------------------------------------------------------------------- 1 | baseUri = config('services.authors.base_uri'); 16 | $this->secret = config('services.authors.secret'); 17 | } 18 | 19 | public function getAuthors(){ 20 | return $this->performRequest('GET','/authors'); 21 | } 22 | public function getAuthor($author){ 23 | return $this->performRequest('GET',"/authors/{$author}"); 24 | } 25 | public function createAuthor($data) { 26 | return $this->performRequest('POST','/authors',$data); 27 | } 28 | public function editAuthor($data, $author) { 29 | return $this->performRequest('PUT',"/authors/{$author}",$data); 30 | } 31 | public function deleteAuthor($author) { 32 | return $this->performRequest('DELETE',"/authors/{$author}"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Services/BookService.php: -------------------------------------------------------------------------------- 1 | baseUri = config('services.books.base_uri'); 19 | $this->secret = config('services.authors.secret'); 20 | } 21 | public function getBooks(){ 22 | return $this->performRequest('GET','/books'); 23 | } 24 | public function getBook($book){ 25 | return $this->performRequest('GET',"/books/{$book}"); 26 | } 27 | public function createBook($data) { 28 | return $this->performRequest('POST','/books',$data); 29 | } 30 | public function editBook($data, $book) { 31 | return $this->performRequest('PUT',"/books/{$book}",$data); 32 | } 33 | public function deleteBook($book) { 34 | return $this->performRequest('DELETE',"/books/{$book}"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 26 | } 27 | 28 | /** 29 | * Handle an incoming request. 30 | * 31 | * @param \Illuminate\Http\Request $request 32 | * @param \Closure $next 33 | * @param string|null $guard 34 | * @return mixed 35 | */ 36 | public function handle($request, Closure $next, $guard = null) 37 | { 38 | if ($this->auth->guard($guard)->guest()) { 39 | return response('Unauthorized.', 401); 40 | } 41 | 42 | return $next($request); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make( 32 | 'Illuminate\Contracts\Console\Kernel' 33 | ); 34 | 35 | exit($kernel->handle(new ArgvInput, new ConsoleOutput)); 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "laravel/lumen", 3 | "description": "The Laravel Lumen Framework.", 4 | "keywords": ["framework", "laravel", "lumen"], 5 | "license": "MIT", 6 | "type": "project", 7 | "require": { 8 | "php": ">=7.1.3", 9 | "dusterio/lumen-passport": "^0.2.15", 10 | "guzzlehttp/guzzle": "^6.3", 11 | "illuminate/mail": "^5.7", 12 | "laravel/lumen-framework": "5.8.*", 13 | "tymon/jwt-auth": "^1.0", 14 | "vlucas/phpdotenv": "^3.3" 15 | }, 16 | "require-dev": { 17 | "fzaninotto/faker": "^1.4", 18 | "phpunit/phpunit": "^7.0", 19 | "mockery/mockery": "^1.0" 20 | }, 21 | "autoload": { 22 | "classmap": [ 23 | "database/seeds", 24 | "database/factories" 25 | ], 26 | "psr-4": { 27 | "App\\": "app/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "classmap": [ 32 | "tests/" 33 | ] 34 | }, 35 | "scripts": { 36 | "post-root-package-install": [ 37 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 38 | ] 39 | }, 40 | "config": { 41 | "preferred-install": "dist", 42 | "sort-packages": true, 43 | "optimize-autoloader": true 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Controllers/AuthorsController.php: -------------------------------------------------------------------------------- 1 | authorService = $authorService; 25 | } 26 | 27 | public function index() :JsonResponse 28 | { 29 | return $this->validResponse($this->authorService->getAuthors()); 30 | } 31 | 32 | public function store(Request $request) :JsonResponse 33 | { 34 | return $this->validResponse($this->authorService->createAuthor($request->all())); 35 | } 36 | 37 | public function show($author) :JsonResponse 38 | { 39 | return $this->validResponse($this->authorService->getAuthor($author)); 40 | } 41 | 42 | public function update(Request $request, $author) :JsonResponse 43 | { 44 | return $this->validResponse($this->authorService->editAuthor(($request->all()), 45 | $author)); 46 | } 47 | 48 | public function destroy($author) :JsonResponse 49 | { 50 | return $this->validResponse($this->authorService->deleteAuthor($author)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/Http/Controllers/BooksController.php: -------------------------------------------------------------------------------- 1 | bookService = $bookService; 28 | $this->authorService = $authorService; 29 | } 30 | 31 | public function index() :JsonResponse 32 | { 33 | return $this->successResponse($this->bookService->getBooks()); 34 | } 35 | 36 | public function store(Request $request) :JsonResponse 37 | { 38 | $this->authorService->getAuthor($request->author_id); 39 | 40 | return $this->successResponse($this->bookService->createBook($request->all())); 41 | } 42 | 43 | public function show($book) :JsonResponse 44 | { 45 | return $this->successResponse($this->bookService->getBook($book)); 46 | } 47 | 48 | public function update(Request $request, $book) :JsonResponse 49 | { 50 | return $this->successResponse($this->bookService->editBook(($request->all()), 51 | $book)); 52 | } 53 | 54 | public function destroy($book) :JsonResponse 55 | { 56 | return $this->successResponse($this->bookService->deleteBook($book)); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | group(['middleware' => 'auth:api'], function () use ($router){ 15 | $router->get('/users/me','UsersController@me'); 16 | }); 17 | 18 | $router->group(['middleware' => 'client.credentials'], function () use ($router){ 19 | $router->get('/authors','AuthorsController@index'); 20 | $router->post('/authors','AuthorsController@store'); 21 | $router->get('/authors/{author}','AuthorsController@show'); 22 | $router->put('/authors/{author}','AuthorsController@update'); 23 | $router->patch('/authors/{author}','AuthorsController@update'); 24 | $router->delete('/authors/{author}','AuthorsController@destroy'); 25 | 26 | $router->get('/books','BooksController@index'); 27 | $router->post('/books','BooksController@store'); 28 | $router->get('/books/{book}','BooksController@show'); 29 | $router->put('/books/{book}','BooksController@update'); 30 | $router->patch('/books/{book}','BooksController@update'); 31 | $router->delete('/books/{book}','BooksController@destroy'); 32 | 33 | $router->get('/users','UsersController@index'); 34 | $router->post('/users','UsersController@store'); 35 | $router->get('/users/{user}','UsersController@show'); 36 | $router->put('/users/{user}','UsersController@update'); 37 | $router->patch('/users/{user}','UsersController@update'); 38 | $router->delete('/users/{user}','UsersController@destroy'); 39 | }); 40 | -------------------------------------------------------------------------------- /app/Http/Controllers/UsersController.php: -------------------------------------------------------------------------------- 1 | validResponse($users, Response::HTTP_OK); 32 | } 33 | 34 | public function store(Request $request) :string 35 | { 36 | 37 | $rules = [ 38 | 'name' => 'required|max:255', 39 | 'email' => 'required|email|unique:users,email', 40 | 'password' => 'required|confirmed|min:8', 41 | ]; 42 | 43 | $this->validate($request, $rules); 44 | $fields = $request->all(); 45 | $fields['password'] = Hash::make($request->password); 46 | 47 | $user = Users::create($fields); 48 | 49 | return $this->validResponse($user, Response::HTTP_CREATED); 50 | } 51 | 52 | public function show($book) :string 53 | { 54 | $user = Users::findOrFail($book); 55 | 56 | return $this->validResponse($book); 57 | } 58 | 59 | public function update(Request $request, $user) :string 60 | { 61 | $rules = [ 62 | 'name' => 'max:255', 63 | 'email' => 'email|unique:user,email,'.$user, 64 | 'password' => 'confirmed|min:8', 65 | ]; 66 | 67 | $this->validate($request, $rules); 68 | $user = Users::findOrFail($user); 69 | 70 | if ($request->has('password')) { 71 | $user->password = Hash::make($request->password); 72 | } 73 | 74 | $user->fill($request->all()); 75 | 76 | if ($user->isClean()) { 77 | return $this->errorResponse('At least one value should change', Response::HTTP_UNPROCESSABLE_ENTITY); 78 | } 79 | 80 | $user->save(); 81 | 82 | return $this->validResponse($user, Response::HTTP_CREATED); 83 | } 84 | 85 | public function destroy($user) :string 86 | { 87 | $user = Users::findOrFail($user); 88 | $user->delete(); 89 | 90 | return $this->validResponse($user); 91 | } 92 | 93 | public function me(Request $request) :JsonResponse 94 | { 95 | return $this->validResponse($request->user()); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | getStatusCode(); 55 | $message = Response::$statusTexts[$code]; 56 | return $this->errorResponse($message, $code); 57 | } 58 | if($exception instanceof ModelNotFoundException){ 59 | $model = strtolower(class_basename($exception->getModel())); 60 | return $this->errorResponse("Dose exist instance of {$model} with this id", Response::HTTP_NOT_FOUND); 61 | } 62 | if($exception instanceof AuthorizationException){ 63 | return $this->errorResponse($exception->getMessage(), Response::HTTP_FORBIDDEN); 64 | } 65 | if($exception instanceof AuthenticationException){ 66 | return $this->errorResponse($exception->getMessage(), Response::HTTP_UNAUTHORIZED); 67 | } 68 | if($exception instanceof ValidationException){ 69 | $errors = $exception->errors(); 70 | return $this->errorResponse($errors, Response::HTTP_UNPROCESSABLE_ENTITY); 71 | } 72 | if($exception instanceof ClientException){ 73 | $message = $exception->getResponse()->getBody(); 74 | $code = $exception->getCode(); 75 | return $this->errorMessage($message, $code); 76 | } 77 | if(env('APP_DEBUG', false)) { 78 | return parent::render($request, $exception); 79 | } 80 | return $this->errorResponse('unexpected error', Response::HTTP_INTERNAL_SERVER_ERROR); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => env('AUTH_GUARD', 'api'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Authentication Guards 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Next, you may define every authentication guard for your application. 26 | | Of course, a great default configuration has been defined for you 27 | | here which uses session storage and the Eloquent user provider. 28 | | 29 | | All authentication drivers have a user provider. This defines how the 30 | | users are actually retrieved out of your database or other storage 31 | | mechanisms used by this application to persist your user's data. 32 | | 33 | | Supported: "session", "token" 34 | | 35 | */ 36 | 37 | 'guards' => [ 38 | 'api' => [ 39 | 'driver' => 'passport', 40 | 'provider' => 'users' 41 | ], 42 | ], 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | User Providers 47 | |-------------------------------------------------------------------------- 48 | | 49 | | All authentication drivers have a user provider. This defines how the 50 | | users are actually retrieved out of your database or other storage 51 | | mechanisms used by this application to persist your user's data. 52 | | 53 | | If you have multiple user tables or models you may configure multiple 54 | | sources which represent each model / table. These sources may then 55 | | be assigned to any extra authentication guards you have defined. 56 | | 57 | | Supported: "database", "eloquent" 58 | | 59 | */ 60 | 61 | 'providers' => [ 62 | 'users' => [ 63 | 'driver' => 'eloquent', 64 | 'model' => \App\Users::class, 65 | ], 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | Resetting Passwords 71 | |-------------------------------------------------------------------------- 72 | | 73 | | Here you may set the options for resetting passwords including the view 74 | | that is your password reset e-mail. You may also set the name of the 75 | | table that maintains all of the reset tokens for your application. 76 | | 77 | | You may specify multiple password reset configurations if you have more 78 | | than one user table or model in the application and you want to have 79 | | separate password reset settings based on the specific user types. 80 | | 81 | | The expire time is the number of minutes that the reset token should be 82 | | considered valid. This security feature keeps tokens short-lived so 83 | | they have less time to be guessed. You may change this as needed. 84 | | 85 | */ 86 | 87 | 'passwords' => [ 88 | // 89 | ], 90 | 91 | ]; 92 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | bootstrap(); 8 | 9 | /* 10 | |-------------------------------------------------------------------------- 11 | | Create The Application 12 | |-------------------------------------------------------------------------- 13 | | 14 | | Here we will load the environment and create the application instance 15 | | that serves as the central piece of this framework. We'll use this 16 | | application as an "IoC" container and router for this framework. 17 | | 18 | */ 19 | 20 | $app = new Laravel\Lumen\Application( 21 | dirname(__DIR__) 22 | ); 23 | 24 | $app->withFacades(); 25 | 26 | $app->withEloquent(); 27 | 28 | $app->configure('services'); 29 | $app->configure('auth'); 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Register Container Bindings 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Now we will register a few bindings in the service container. We will 37 | | register the exception handler and the console kernel. You may add 38 | | your own bindings here if you like or you can make another file. 39 | | 40 | */ 41 | 42 | $app->singleton( 43 | Illuminate\Contracts\Debug\ExceptionHandler::class, 44 | App\Exceptions\Handler::class 45 | ); 46 | 47 | $app->singleton( 48 | Illuminate\Contracts\Console\Kernel::class, 49 | App\Console\Kernel::class 50 | ); 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | Register Middleware 55 | |-------------------------------------------------------------------------- 56 | | 57 | | Next, we will register the middleware with the application. These can 58 | | be global middleware that run before and after each request into a 59 | | route or middleware that'll be assigned to some specific routes. 60 | | 61 | */ 62 | 63 | $app->middleware([ 64 | ]); 65 | 66 | $app->routeMiddleware([ 67 | 'auth' => \App\Http\Middleware\Authenticate::class, 68 | 'client.credentials' => \Laravel\Passport\Http\Middleware\CheckClientCredentials::class, 69 | ]); 70 | 71 | // configure mail aliases 72 | $app->configure('mail'); 73 | $app->alias('mailer', Illuminate\Mail\Mailer::class); 74 | $app->alias('mailer', Illuminate\Contracts\Mail\Mailer::class); 75 | $app->alias('mailer', Illuminate\Contracts\Mail\MailQueue::class); 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Register Service Providers 80 | |-------------------------------------------------------------------------- 81 | | 82 | | Here we will register all of the application's service providers which 83 | | are used to bind services into the container. Service providers are 84 | | totally optional, so you are not required to uncomment this line. 85 | | 86 | */ 87 | 88 | // $app->register(App\Providers\AppServiceProvider::class); 89 | $app->register(App\Providers\AuthServiceProvider::class); 90 | $app->register(Laravel\Passport\PassportServiceProvider::class); 91 | $app->register(Dusterio\LumenPassport\PassportServiceProvider::class); 92 | $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::class); 93 | $app->register(App\Providers\EventServiceProvider::class); 94 | $app->register(Illuminate\Mail\MailServiceProvider::class); 95 | 96 | /* 97 | |-------------------------------------------------------------------------- 98 | | Load The Application Routes 99 | |-------------------------------------------------------------------------- 100 | | 101 | | Next we will include the routes file so that they can all be added to 102 | | the application. This will provide all of the URLs the application 103 | | can respond to, as well as the controllers that may handle them. 104 | | 105 | */ 106 | 107 | $app->router->group([ 108 | 'namespace' => 'App\Http\Controllers', 109 | ], function ($router) { 110 | require __DIR__.'/../routes/web.php'; 111 | }); 112 | 113 | return $app; 114 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | SMTP Host Address 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may provide the host address of the SMTP server used by your 27 | | applications. A default option is provided that is compatible with 28 | | the Mailgun mail service which will provide reliable deliveries. 29 | | 30 | */ 31 | 32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | SMTP Host Port 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This is the SMTP port used by your application to deliver e-mails to 40 | | users of the application. Like the host we have set this value to 41 | | stay compatible with the Mailgun e-mail application by default. 42 | | 43 | */ 44 | 45 | 'port' => env('MAIL_PORT', 587), 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Global "From" Address 50 | |-------------------------------------------------------------------------- 51 | | 52 | | You may wish for all e-mails sent by your application to be sent from 53 | | the same address. Here, you may specify a name and address that is 54 | | used globally for all e-mails that are sent by your application. 55 | | 56 | */ 57 | 58 | 'from' => [ 59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 60 | 'name' => env('MAIL_FROM_NAME', 'Example'), 61 | ], 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | E-Mail Encryption Protocol 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Here you may specify the encryption protocol that should be used when 69 | | the application send e-mail messages. A sensible default using the 70 | | transport layer security protocol should provide great security. 71 | | 72 | */ 73 | 74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | SMTP Server Username 79 | |-------------------------------------------------------------------------- 80 | | 81 | | If your SMTP server requires a username for authentication, you should 82 | | set it here. This will get used to authenticate with your server on 83 | | connection. You may also set the "password" value below this one. 84 | | 85 | */ 86 | 87 | 'username' => env('MAIL_USERNAME'), 88 | 89 | 'password' => env('MAIL_PASSWORD'), 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Sendmail System Path 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When using the "sendmail" driver to send e-mails, we will need to know 97 | | the path to where Sendmail lives on this server. A default path has 98 | | been provided here, which will work well on most of your systems. 99 | | 100 | */ 101 | 102 | 'sendmail' => '/usr/sbin/sendmail -bs', 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Markdown Mail Settings 107 | |-------------------------------------------------------------------------- 108 | | 109 | | If you are using Markdown based email rendering, you may configure your 110 | | theme and component paths here, allowing you to customize the design 111 | | of the emails. Or, you may simply stick with the Laravel defaults! 112 | | 113 | */ 114 | 115 | 'markdown' => [ 116 | 'theme' => 'default', 117 | 118 | 'paths' => [ 119 | resource_path('views/vendor/mail'), 120 | ], 121 | ], 122 | 123 | ]; 124 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = Lumen Api Gateway Microservice 2 | 3 | This project was done as a part of 3 *Microservices* project using *Lumen* and *Laradock* 4 | Can be used as a stand a lone project and part of the *Microservices* project. 5 | 6 | = What is an API Gateway 7 | 8 | An API gateway takes all API calls from clients, then routes them to the appropriate microservice with request routing, composition, and protocol translation. Typically it handles a request by invoking multiple microservices and aggregating the results, to determine the best path. It can translate between web protocols and web‑unfriendly protocols that are used internally. 9 | This part is the first point that the client connect to when trying to use the microserivces of 10 | a given system also provides auth services for the client and manges access to the microservices. 11 | 12 | image::file.png[] 13 | vices.io/i/apigateway.jpg 14 | ____ 15 | 16 | Lumen is designed for building lightning fast micro-services and APIs 17 | 18 | ____ 19 | 20 | *Laradock* is a full PHP development environment for Docker that Includes prepackaged Docker Images, all preconfigured to provide a wonderful PHP development environment. Laradock is well known in the Laravel/lumen community, as the project started with single focus on running Laravel projects on Docker. 21 | 22 | == Usefull Links 23 | 24 | * https://laradock.io/[Laradock] 25 | * https://lumen.laravel.com/[Lumen] 26 | 27 | == Requirements 28 | 29 | To be able to run this project one needs the following technologies: 30 | 31 | * https://www.docker.com/[Docker] -> Laradock uses Docker 32 | * https://getcomposer.org/[Composer] -> No need if one uses Laradock/Docker 33 | 34 | if one want to start Lumen from scratch Composer is need as a package manager. 35 | 36 | == Instructions 37 | 38 | Following the instructions one should be able to run this project or at least have a good base how to start a Lumen project using Laradock. 39 | 40 | . `git clone git@github.com:ahmedalaahagag/api-gateway-php.git` 41 | . Rename `.env.example` file to `.env`. The 42 | .env file is the environment file that deals with project configurations like database credentials, api keys, debug mode, application keys etc and this file is out of version control. 43 | . Set your application key to a random string. Typically, this string should be 32 characters long. In .env file it is called eg 44 | `APP_KEY=akkfjvlakengoemvgkcgelapchyekci` 45 | . Add `MICRO_SERVICE_BASE_URI` which should point to the address of your microserivce 46 | ex.`AUTHORS_SERVICE_BASE_URI="http://IP_ADDRESS"` if you are using localy with laradock use your local network ip address which you can get from 47 | Linux or MacOs `ifconfig` or Windows from `network adapter properties`. 48 | . Add `MICRO_SERVICE_SECRET` which should be added to your microservice to secure the connection between the api gateway 49 | and the microservice ex.`AUTHORS_SERVICE_SECRET=ECvSZ5O6P9x1GP1fvbtEVktoN358BofH` 50 | should be sent with every api call to this microservice using the header `Authorization:` 51 | ex.`Authorization:U0ZyuUhUrNgmLu9TIWJJay30gCT8UOyd` 52 | . Laradock clone it inside the project folder `git clone https://github.com/laradock/laradock.git` 53 | 54 | == To run this project as a standalone project 55 | 56 | . `cd laradock` 57 | . `cp env-example .env` 58 | . `docker-compose up -d nginx mysql phpmyadmin workspace` => To start the server 59 | . `docker-compose exec workspace bash` => to get access to virtual machine and here one can execute any artisan command 60 | . Run `composer install` => to install all php dependencies. This will create a vendor folder which is the core lumen framework 61 | . Inside `.env` file in the project root update `DB_HOST=mysql` 62 | . With SQL tool as `PhpMyAdmin` which is already provided by *Laradock* at port `localhost:8080` or similar connect to the MySQL to create a new DB. Or use the Docker MySQL workspace bash to use commands instead. 63 | . Default values `host:mysql username:root password:root` 64 | . Update database name and else in `.env` 65 | 66 | Remember all the *Docker* commands have to be run it under *Laradock* folder as there the Docker files are placed. 67 | 68 | If one wants to run this project as it is after `composer install` run migration as `php artisan migrate` to update the DB with the right tables. Then seed with `php artisan db:seed` to populate the DB with some fake data. 69 | 70 | == Securing the Api gateway 71 | This gateway is secured using `lumen/passport` is a lumen package which authorizes and authenticates users 72 | to start using the gateway your client should first request a token using 73 | `localhost/oauth/token` using provided `client_id` and `client_secret` 74 | to generate those you need to run `docker-compose exec workspace bash` and use the following command after installing passport 75 | `php artisan passport:install` 76 | which a typical Oauth process. 77 | If you are not familier with the process refer to 78 | https://github.com/dusterio/lumen-passport 79 | 80 | == Adding a new microservice 81 | After writing your microservice code 82 | 83 | . obtain the address for the microservice. 84 | . in `.env` file add 85 | 86 | MICRO_SERVICE_BASE_URI="http://IP_ADDRESS:PORT" 87 | MICRO_SERVICE_SECRET= 88 | 89 | Which is how your api gateway gets to your microservice 90 | 91 | . in `app/Http/Controllers` dir add the oprations that you want to perform with your api gatway 92 | . in `app/Http/Services` dir add the api call using `ConsumeExternalService` trait or write your own 93 | `ConsumeExternalService` is a guzzle wrapper 94 | http://docs.guzzlephp.org/en/stable/ 95 | . in `routes/web.php` add your new routes 96 | 97 | And like this you've added a new microservice. 98 | 99 | Note : This is not the best practice though read more on service discovery 100 | https://www.nginx.com/blog/service-discovery-in-a-microservices-architecture/ 101 | 102 | == Troubleshoot some possible issues 103 | 104 | It is possible one has issues with connecting to MySQL image of Docker. A possible solution as follows: 105 | 106 | From terminal 107 | 108 | ---- 109 | $ docker-compose exec mysql bash 110 | $ mysql -u root -p 111 | 112 | # Execute commands 113 | ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root'; 114 | ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'root'; 115 | ALTER USER 'default'@'%' IDENTIFIED WITH mysql_native_password BY 'secret'; 116 | ---- 117 | 118 | May be need to restart the container after the changes 119 | 120 | ---- 121 | $ docker-compose down 122 | $ docker-compose up -d nginx mysql 123 | ---- 124 | 125 | == Tutorial 126 | 127 | How to create a *Simple Gateway*. Step by step explanations to get start with *Lumen* 128 | 129 | *1) Create a Lumen project* 130 | 131 | First one need to install Lumen via Composer: 132 | 133 | ---- 134 | composer global require "laravel/lumen-installer" 135 | ---- 136 | 137 | Then can run: 138 | 139 | ---- 140 | lumen new ApiGateWay 141 | ---- 142 | 143 | *2) Clone Laradock inside the ApiGateWay folder project* 144 | 145 | The steps above shows what to do with Laradock and Docker parts. 146 | 147 | *3) Connect to the MySQL container* 148 | One can connect via a program like PhpMyAdmin or MysqlWorkBench or else to the MySQL container. Then need to create the DB. 149 | 150 | Example of a connection set up: 151 | image:doc/Edit_Connection_Laradock__MySQL.png[connection_db] 152 | 153 | Remember the name of the DB need to be put inside the `.env` file along with the credetials of it. 154 | 155 | ---- 156 | DB_CONNECTION=mysql 157 | DB_HOST=mysql -> This need to be in this way and if any connection issues please refer to the troubleshoot section above 158 | DB_PORT=3306 -> Change this if you use a different port 159 | DB_DATABASE= < DB_NAME > < ex. gateway > 160 | DB_USERNAME= < DB_USERNAME > < ex. root > 161 | DB_PASSWORD= < DB_PASSWORD > < ex. root > 162 | ---- 163 | 164 | After one done the first preliminary set up steps, then is the time to move forward creating the API itself. 165 | 166 | *4) Eloquent* 167 | 168 | In simple words allows calling built-in functions instead of writing complex queries. 169 | The Eloquent ORM includes *Laravel/Lumen* which provides a beautiful, simple *ActiveRecord* implementation for working with the database. 170 | Each database table has a corresponding *Model* which is used to interact with that table. Models allow you to query for data in your tables, as well as insert new records into the table. For example, one can say `Users::all()` to get all the users inside users table rather than writing `select * from users`. 171 | Where Users in `Users::all() is a`model`. 172 | 173 | Then to use *Eloquent* uncomment the `$app->withEloquent()` 174 | in your `bootstrap/app.php` 175 | 176 | *5) Facades* 177 | 178 | A *facade* class is a wrapper around a few classes serving a particular purpose to make a series of steps required to perform a task in a single function call. 179 | 180 | Then uncomment the `$app->withFacades()` call inside `bootstrap/app.php` file to use *Laravel Facade*. 181 | 182 | *6) Users* 183 | Then inside the *app* folder, will create `Users.php`. It is called a model in *MVC framework*. 184 | It will reflect *users table* inside database which has not been created yet Inside this model will have set some fillable `fields =>name` and `gender` and `country` 185 | as all *Eloquent models* protect against mass-assignment by default. A *mass-assignment* vulnerability occurs when a user passes an unexpected HTTP parameter through a request, and that parameter changes a column in your database you did not expect 186 | 187 | See more at https://laravel.com/docs/5.7/eloquent#mass-assignment[mass-assignment] 188 | 189 | *7)Create a migration* 190 | 191 | To create a migration one need to be inside the *Docker container workspace*: 192 | 193 | ---- 194 | docker-compose exec workspace bash 195 | ---- 196 | 197 | Then: 198 | 199 | ---- 200 | root@688df818e9b7:/var/www# php artisan make:migration create_users_table 201 | ---- 202 | 203 | This will create migration file inside `database/migrations` 204 | 205 | Example: `2020_02_27_153519_create_users_table.php` 206 | 207 | A migration file usually defines the schema of the database table. 208 | 209 | See more at https://laravel.com/docs/5.7/migrations[migrations] 210 | 211 | Then run command 212 | 213 | ---- 214 | php artisan migrate 215 | ---- 216 | 217 | This will migrate schema to database according to what is present in migration file. Now your database will have *users table*. 218 | This is how Eloquent makes it so easy to create tables, share this schema with the team and use its simple functions to generate complex sql queries. 219 | 220 | *8) Fake data to use for the test of the API* 221 | 222 | Now the issue how we test the API if we do not have any data to 223 | test actually. 224 | 225 | *Lumen* has a very fine way to create dummy data. It is called *Model Factories*. That uses https://github.com/fzaninotto/Faker[Faker] package behind the scenes. Let's dive into. 226 | 227 | Inside `database/factories/ModelFactory.php` will define a factory for each table (1 only for users table in this case). A factory is a suitable word because a factory creates object based on rules defined inside the factory. 228 | 229 | Now we need the a *seeder class* to call this factory to start creating objects and tell it a number to produce as well. So command `php artisan make:seeder UsersTableSeeder. 230 | 231 | Alternatively you can create a factory as in *database/factories/ModelFactory.php* to create objects of Users + 232 | Will ask it to create 50 objects whenever it is called. Inside 233 | `database/seeds/DatabaseSeeder.php` call `UsersFactory`. 234 | Now we will run `php artisan db:seed` command to seed the database. Which will call `run()` in `DatabaseSeeder.php` and seed all listed seeders. 235 | We now have 50 dummy records inside users table. 236 | 237 | Example: 238 | ![seeds](doc/Screenshot 2019-04-03 at 23.57.20.png) 239 | 240 | *9) API end points* 241 | 242 | If we go to `routes/web.php` here is we define our endpoints/routes. For example if one wants to get all books one will set an endpoint with `url books/all`. 243 | See the file. One used `Users::all()` (in a callback function) 244 | which is *Eloquent* way to fetch all the results for a 245 | model which has also been discussed earlier. 246 | 247 | Now if one hit the endpoint through *Postman* 248 | Attached is a collection that can be imported to *Postman* 249 | `Gateway.postman_collection.json` 250 | or visit in browser`localhost/users` one should see all users in json. 251 | 252 | = What to expect with the code 253 | 254 | * Standardized response format `ApiResponder.php` 255 | * Standardized Api requests `ConsumeExternalService.php` 256 | * Independent services for microservices `AuthorService.php` and `BookService.php` 257 | * `Secret Key` protected endpoints `AuthenticateAccess.php` 258 | * Standardized exception response format `Handler.php` 259 | * RESTful Based API format `web.php` 260 | 261 | = Response Example 262 | 263 | [source] 264 | ---- 265 | All APIs should be calls with Authorization header 266 | `Authorization` 267 | Which is the key provided in the .env file with the key `ACCEPTED_SECRETS` 268 | 269 | Get example 270 | 271 | API : POST localhost/oauth/token 272 | Response : 273 | { 274 | "token_type": "Bearer", 275 | "expires_in": 31622400, 276 | "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjAxYzdjYzIwYWE2ZWM2ODg1NzQxNjVjYTc1ZDJhMzk2MjkyOTVkYTAwNTM2OTQ4OGI4MjNkOGE5OTdjYmYyMjc2NGQwZTdjZjM4MGJkOWJlIn0.eyJhdWQiOiI0IiwianRpIjoiMDFjN2NjMjBhYTZlYzY4ODU3NDE2NWNhNzVkMmEzOTYyOTI5NWRhMDA1MzY5NDg4YjgyM2Q4YTk5N2NiZjIyNzY0ZDBlN2NmMzgwYmQ5YmUiLCJpYXQiOjE1ODI5ODQzODcsIm5iZiI6MTU4Mjk4NDM4NywiZXhwIjoxNjE0NjA2Nzg3LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.b8OwcPVlfoYDtWBoBE64A-5Aur0FEkTyv0vBLjWqA1mjOkRXW1EyCHNzxEZsE4e3oPgjrsZcN37YlxetlnQfOtaxfpSTF9lSL7xXmm2nKhA46XcnyFgSeV3H8X-Msn22q0hiouSXdMcuQiXyWFTn3vDKDleq5s7kXUoI4MTeFScXnf_L8HQG2di7WCOKQppWTPtZA0ArnDoIBesoUxSu0J8CFg7qP1DdjxeYx0HKNU4FKytuJT-_i-aRAsPG5k-Z7b7AaSwmS5EDmmDjcnwApRLFcerBApaWJx9L26Rg7m0RT6xGCXL31k_454YIvmRMJVMbbwo-2zBPjrbWgKowoQ29MvVjy4BDuzM900QW3W3KjpiRDO7ZV066s0xe8wFTZrq7g4qK0DpG64Sf1k1mZYL3IoMzUvNPrPVhiLSHhtmUE3aumYt90euaVcafPTREUN3DYbvS5BY3RApt1nJPe3c2KfP7ARqi12NKsX3N8RkekS6KXwq1tPZnIcHFip2UJSwpu0kgDJzZAYVfrdkH7UtEmssEriRm__59IcSLQedyHiq2ROMjHHr4VoWdTLpd4sGHTYGqQIn0jj2RdKEmscl0rlxYNWC2krVgEWBTrvY42NcuzkQGoasMTt1oIf-Kcnf-x_UXU2cP4Q7Hi8aQUuQ0a_Q1CntZWvyvFFe2A5o", 277 | "refresh_token": "def50200a5d83b16a4a238488ba574d6fb45c8e2196ded5c57d47709e2b8dd827ea071ece4f8ba01e7749a96712e43bb4e1360f566ba5933fada03a6253d29b5d001e7d6c430177cba538b053afbf75b7c263390e7f0ee519f1f9acb398a35dac6c9dd678029f3f190019e5037a9e9ae685a4afef4cc777883948f02fbff755a21b38ec28364b71ee0c3a72174d83aa574c8df17b0e1a6f65e1417ed085d383690999a53336ea2e33e19f7c1e9f7d9d74b0d37203e5284fa2ac6b6cf9bef509182fb9645a62b7dbbd8ddf44a39c340755ad2474c2c95428e24fbd850ce4e76dbf94195db39033e57cc002d73b847864373f09cc3c9c6a5fc7644a7edbfd0aa596ae001fbf5a106327f10f69995ec3b13cd7ad9dd6ca12858cdf41b98d8d4b55e5670a01422edf75ab321546f41ea8de75727b53bc4d5ad3f960d00ddefcc8c823ade553e548803fdc1e21557481dfd3c164684c91df4262eec67f3e1284f61db952a4cc5" 278 | } 279 | 280 | Error example 281 | 282 | API : GET localhost/users 283 | Response : 284 | { 285 | "error": "Unauthenticated." 286 | } 287 | ---- 288 | -------------------------------------------------------------------------------- /Gateway.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "info": { 3 | "_postman_id": "8a406aa6-865d-4bb1-9cfc-bd254b49d608", 4 | "name": "Gateway", 5 | "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" 6 | }, 7 | "item": [ 8 | { 9 | "name": "Authors", 10 | "request": { 11 | "method": "GET", 12 | "header": [ 13 | { 14 | "key": "Authorization", 15 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6Ijk4ZmZlMDFhYWFkNzM0ZDNiYWIyMDJlYmRkYjE3NDEwMTgwZmE5ZDVmYWM3MWMzODlmZTcxYzliODFlMzBiMzBiOGIwNjQ5MTNiOTRkNGRhIn0.eyJhdWQiOiIxIiwianRpIjoiOThmZmUwMWFhYWQ3MzRkM2JhYjIwMmViZGRiMTc0MTAxODBmYTlkNWZhYzcxYzM4OWZlNzFjOWI4MWUzMGIzMGI4YjA2NDkxM2I5NGQ0ZGEiLCJpYXQiOjE1ODI5MDc2NDcsIm5iZiI6MTU4MjkwNzY0NywiZXhwIjoxNjE0NTMwMDQ3LCJzdWIiOiIiLCJzY29wZXMiOltdfQ.ftafh-ZJ8f2U20K8xaeLW9knDAMQ4SohZbSb4gi-RMZ-QDuxiJwdIHnhSkl01j5qBjHkixo5H5SErlTzJGccamq7K88cLIIORc6qHf14g9fSxzf_M5BzKeypiepLY9m3Lns-6HSHjgQX_zStimj6pEWh8fhRl7X1BbOQO98rCYO_8ML75wkU4EKzZrKi19ZncrGhDTg5eAG7a7_wTWMeHT4C6ROG_dQiTwjraEeCDV3ooxm6Yivw0he7D2YXrmKtVOWdnnHUyxrHTSNEcDzmDAYDtiqRobwX1eTBpvxkbyZi3skfk2VkWNIsEaZS-HDK-QeYo-16-rOP7w7dYfm0bXp42Be_HFCF2drxJzwLKbBTwrDodKw46knf1EbL_t7I2WWLymde0U1ndR3ugI2c51iBs5-d6YM0bFaTGhHrnUXkqDRAcCEvOP77iGA-HAsA3NgY0w9sKSP5c3HdLrqQ9vGmQG6tZMIE5XLzxqnO4U7-Bg-27rSldt1wXB-qYibbec_SmlBZsUpZkXZNusO6PhF1DKWDZz6DBAYpy-fc3nBP0RuUrsO8joToY_GJGUZ77Wy7J6Tn9vA-zIH3e1w4Y8Y3vc7g2QTanZ0lKrfOALl7cZA8y9ItLyZXgSVwaDqzwtTD4WwLueQe9nlhBUTjM9W7tmHMN3hMnvuWtNZ1g4k", 16 | "type": "text" 17 | } 18 | ], 19 | "url": { 20 | "raw": "localhost/authors", 21 | "host": [ 22 | "localhost" 23 | ], 24 | "path": [ 25 | "authors" 26 | ] 27 | } 28 | }, 29 | "response": [] 30 | }, 31 | { 32 | "name": "Create Author", 33 | "request": { 34 | "method": "POST", 35 | "header": [ 36 | { 37 | "key": "Content-Type", 38 | "name": "Content-Type", 39 | "value": "application/x-www-form-urlencoded", 40 | "type": "text" 41 | } 42 | ], 43 | "body": { 44 | "mode": "urlencoded", 45 | "urlencoded": [ 46 | { 47 | "key": "name", 48 | "value": "Test 3", 49 | "type": "text" 50 | }, 51 | { 52 | "key": "gender", 53 | "value": "male", 54 | "type": "text" 55 | }, 56 | { 57 | "key": "country", 58 | "value": "Germany", 59 | "type": "text" 60 | } 61 | ] 62 | }, 63 | "url": { 64 | "raw": "localhost/authors", 65 | "host": [ 66 | "localhost" 67 | ], 68 | "path": [ 69 | "authors" 70 | ] 71 | } 72 | }, 73 | "response": [] 74 | }, 75 | { 76 | "name": "Get Author", 77 | "request": { 78 | "method": "GET", 79 | "header": [], 80 | "url": { 81 | "raw": "localhost/authors/1", 82 | "host": [ 83 | "localhost" 84 | ], 85 | "path": [ 86 | "authors", 87 | "1" 88 | ] 89 | } 90 | }, 91 | "response": [] 92 | }, 93 | { 94 | "name": "Update Author", 95 | "request": { 96 | "method": "PUT", 97 | "header": [ 98 | { 99 | "key": "Content-Type", 100 | "name": "Content-Type", 101 | "value": "application/x-www-form-urlencoded", 102 | "type": "text" 103 | } 104 | ], 105 | "body": { 106 | "mode": "urlencoded", 107 | "urlencoded": [ 108 | { 109 | "key": "name", 110 | "value": "Test 4", 111 | "type": "text" 112 | }, 113 | { 114 | "key": "gender", 115 | "value": "male", 116 | "type": "text" 117 | }, 118 | { 119 | "key": "country", 120 | "value": "Germany", 121 | "type": "text" 122 | } 123 | ] 124 | }, 125 | "url": { 126 | "raw": "localhost/authors/20", 127 | "host": [ 128 | "localhost" 129 | ], 130 | "path": [ 131 | "authors", 132 | "20" 133 | ] 134 | } 135 | }, 136 | "response": [] 137 | }, 138 | { 139 | "name": "Delete Author", 140 | "request": { 141 | "method": "DELETE", 142 | "header": [ 143 | { 144 | "key": "Content-Type", 145 | "name": "Content-Type", 146 | "value": "application/x-www-form-urlencoded", 147 | "type": "text", 148 | "disabled": true 149 | } 150 | ], 151 | "body": { 152 | "mode": "urlencoded", 153 | "urlencoded": [] 154 | }, 155 | "url": { 156 | "raw": "localhost/authors/21", 157 | "host": [ 158 | "localhost" 159 | ], 160 | "path": [ 161 | "authors", 162 | "21" 163 | ] 164 | } 165 | }, 166 | "response": [] 167 | }, 168 | { 169 | "name": "Books", 170 | "request": { 171 | "method": "GET", 172 | "header": [], 173 | "url": { 174 | "raw": "localhost/books", 175 | "host": [ 176 | "localhost" 177 | ], 178 | "path": [ 179 | "books" 180 | ] 181 | } 182 | }, 183 | "response": [] 184 | }, 185 | { 186 | "name": "Create Book", 187 | "request": { 188 | "method": "POST", 189 | "header": [], 190 | "body": { 191 | "mode": "formdata", 192 | "formdata": [ 193 | { 194 | "key": "title", 195 | "value": "The new book", 196 | "type": "text" 197 | }, 198 | { 199 | "key": "description", 200 | "value": "The new book", 201 | "type": "text" 202 | }, 203 | { 204 | "key": "price", 205 | "value": "2", 206 | "type": "text" 207 | }, 208 | { 209 | "key": "author_id", 210 | "value": "1", 211 | "type": "text" 212 | } 213 | ] 214 | }, 215 | "url": { 216 | "raw": "localhost/books", 217 | "host": [ 218 | "localhost" 219 | ], 220 | "path": [ 221 | "books" 222 | ] 223 | } 224 | }, 225 | "response": [] 226 | }, 227 | { 228 | "name": "Get Book", 229 | "protocolProfileBehavior": { 230 | "disableBodyPruning": true 231 | }, 232 | "request": { 233 | "method": "GET", 234 | "header": [], 235 | "body": { 236 | "mode": "formdata", 237 | "formdata": [ 238 | { 239 | "key": "name", 240 | "value": "Ahmed", 241 | "type": "text" 242 | }, 243 | { 244 | "key": "gender", 245 | "value": "male", 246 | "type": "text" 247 | }, 248 | { 249 | "key": "country", 250 | "value": "Germany", 251 | "type": "text" 252 | } 253 | ] 254 | }, 255 | "url": { 256 | "raw": "localhost/books/151", 257 | "host": [ 258 | "localhost" 259 | ], 260 | "path": [ 261 | "books", 262 | "151" 263 | ] 264 | } 265 | }, 266 | "response": [] 267 | }, 268 | { 269 | "name": "Update Book", 270 | "request": { 271 | "method": "PUT", 272 | "header": [ 273 | { 274 | "key": "Content-Type", 275 | "name": "Content-Type", 276 | "type": "text", 277 | "value": "application/x-www-form-urlencoded" 278 | } 279 | ], 280 | "body": { 281 | "mode": "urlencoded", 282 | "urlencoded": [ 283 | { 284 | "key": "title", 285 | "value": "The new book", 286 | "type": "text" 287 | }, 288 | { 289 | "key": "description", 290 | "value": "The new book", 291 | "type": "text" 292 | }, 293 | { 294 | "key": "price", 295 | "value": "50", 296 | "type": "text" 297 | }, 298 | { 299 | "key": "author_id", 300 | "value": "1", 301 | "type": "text" 302 | } 303 | ] 304 | }, 305 | "url": { 306 | "raw": "localhost/books/151", 307 | "host": [ 308 | "localhost" 309 | ], 310 | "path": [ 311 | "books", 312 | "151" 313 | ] 314 | } 315 | }, 316 | "response": [] 317 | }, 318 | { 319 | "name": "Delete Book", 320 | "request": { 321 | "method": "DELETE", 322 | "header": [], 323 | "body": { 324 | "mode": "formdata", 325 | "formdata": [ 326 | { 327 | "key": "name", 328 | "value": "AhmedAlaa", 329 | "type": "text" 330 | }, 331 | { 332 | "key": "gender", 333 | "value": "male", 334 | "type": "text" 335 | }, 336 | { 337 | "key": "country", 338 | "value": "Germany", 339 | "type": "text" 340 | } 341 | ] 342 | }, 343 | "url": { 344 | "raw": "localhost/books/151", 345 | "host": [ 346 | "localhost" 347 | ], 348 | "path": [ 349 | "books", 350 | "151" 351 | ] 352 | } 353 | }, 354 | "response": [] 355 | }, 356 | { 357 | "name": "Issue Token", 358 | "request": { 359 | "method": "POST", 360 | "header": [ 361 | { 362 | "key": "Content-Type", 363 | "name": "Content-Type", 364 | "value": "application/x-www-form-urlencoded", 365 | "type": "text" 366 | } 367 | ], 368 | "body": { 369 | "mode": "urlencoded", 370 | "urlencoded": [ 371 | { 372 | "key": "grant_type", 373 | "value": "password", 374 | "type": "text" 375 | }, 376 | { 377 | "key": "client_id", 378 | "value": "2", 379 | "type": "text" 380 | }, 381 | { 382 | "key": "client_secret", 383 | "value": "client_secret", 384 | "type": "text" 385 | }, 386 | { 387 | "key": "scope", 388 | "value": "*", 389 | "type": "text" 390 | }, 391 | { 392 | "key": "username", 393 | "value": "test@test.com", 394 | "type": "text" 395 | }, 396 | { 397 | "key": "password", 398 | "value": "Germany123", 399 | "type": "text" 400 | } 401 | ] 402 | }, 403 | "url": { 404 | "raw": "localhost/oauth/token", 405 | "host": [ 406 | "localhost" 407 | ], 408 | "path": [ 409 | "oauth", 410 | "token" 411 | ] 412 | } 413 | }, 414 | "response": [] 415 | }, 416 | { 417 | "name": "Create User", 418 | "request": { 419 | "method": "POST", 420 | "header": [ 421 | { 422 | "key": "", 423 | "value": "", 424 | "type": "text" 425 | } 426 | ], 427 | "body": { 428 | "mode": "formdata", 429 | "formdata": [ 430 | { 431 | "key": "name", 432 | "value": "test", 433 | "type": "text" 434 | }, 435 | { 436 | "key": "email", 437 | "value": "test@test.com", 438 | "type": "text" 439 | }, 440 | { 441 | "key": "password", 442 | "value": "Germany123", 443 | "type": "text" 444 | }, 445 | { 446 | "key": "password_confirmation", 447 | "value": "Germany123", 448 | "type": "text" 449 | } 450 | ] 451 | }, 452 | "url": { 453 | "raw": "localhost/users", 454 | "host": [ 455 | "localhost" 456 | ], 457 | "path": [ 458 | "users" 459 | ] 460 | } 461 | }, 462 | "response": [] 463 | }, 464 | { 465 | "name": "Users", 466 | "request": { 467 | "auth": { 468 | "type": "bearer", 469 | "bearer": [ 470 | { 471 | "key": "token", 472 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImRhMjU0NTA3MDU3MWU4OTc0OGJiNjQ4Mzk0YWFmMmRlZTcwNjhhYWM1MTEwMmRiNTg4ZDM0YzUyZDRkNTgyOGM5ZGFhMTJkNTc1YmFlZjBlIn0.eyJhdWQiOiIyIiwianRpIjoiZGEyNTQ1MDcwNTcxZTg5NzQ4YmI2NDgzOTRhYWYyZGVlNzA2OGFhYzUxMTAyZGI1ODhkMzRjNTJkNGQ1ODI4YzlkYWExMmQ1NzViYWVmMGUiLCJpYXQiOjE1ODI5MTIxNDcsIm5iZiI6MTU4MjkxMjE0NywiZXhwIjoxNjE0NTM0NTQ3LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.bbhcN2OLO3y_pVIfhtdiHxNI1myDGiObWC_5NND4duJkkGmUzZCTjHEI4pMKXIMJMnADBGulTxB9HybqaxN0rNiqE99X1_tJcwHt3OZwx32_gg9Sg9nGJv4mgRylkESJIW7j1aLwPZxQaPP_5P_0ZIYVaQKRMNRkrsaKcLXJPjK90Uqrj2uvoHorfG1-rKyJVo7iu-6fjojbR1-05yf0LASLphcQSoP2GUYSFNhTtweLosflaPo_rE8OUsvgBF7yE9PojVqf4xbt8ffzNUYZF5sr-jao72ZHkHxPdhNHTZknWvuSdCnDuJhTnRausNbK_PDawam3TGnEihZId3VhnnHL65MV6K4x_hmqYDoKr8u-X7yrSnFUh8xQejoCuDITsG5tN8BFUW1n_1_GJp5AkJxFsThMNfjUEV76BLdngFusxTPo5Ke0oW0nRhnhcR4cbpvNmIZ-Eb8WDx8L3Aj5XIH-dexV_brcPMCWgen7HCQ-7HS_XBDbdtehtkMMdxYvJu7Y4bpmm-JW7LxPQkMWy3eruw_S3ZGQAD3zvY2JmxaqARxFL2qqpBGsByEZo_SQPUZwwJGAey2WISR0eQiOOvQ2wJIax6Z8r8DBeV4DXPUJfOsYD22pByEtliopKMwdeKwRAxgD5EppaYdCpSrRv6P0PfpOiHqKRcrzdkd30L4", 473 | "type": "string" 474 | } 475 | ] 476 | }, 477 | "method": "GET", 478 | "header": [], 479 | "url": { 480 | "raw": "localhost/users", 481 | "host": [ 482 | "localhost" 483 | ], 484 | "path": [ 485 | "users" 486 | ] 487 | } 488 | }, 489 | "response": [] 490 | }, 491 | { 492 | "name": "Get User Request", 493 | "protocolProfileBehavior": { 494 | "disableBodyPruning": true 495 | }, 496 | "request": { 497 | "auth": { 498 | "type": "bearer", 499 | "bearer": [ 500 | { 501 | "key": "token", 502 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjgxNmM4NGJkOWMwNTYwMjhkMDM1YjM4ZjgxMmIxNjI3NDk2YWRiYjg5YzJhNTZiZDU0ODU0OGU1YjRkMTRjM2I5ZWM3ODY4OGRjZmE0ZjhhIn0.eyJhdWQiOiIyIiwianRpIjoiODE2Yzg0YmQ5YzA1NjAyOGQwMzViMzhmODEyYjE2Mjc0OTZhZGJiODljMmE1NmJkNTQ4NTQ4ZTViNGQxNGMzYjllYzc4Njg4ZGNmYTRmOGEiLCJpYXQiOjE1ODI5MTIxNjksIm5iZiI6MTU4MjkxMjE2OSwiZXhwIjoxNjE0NTM0NTY5LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.XQocaFqIsKKRErLTXTA98lNZ0tcAc2WhYt639EWxgwhrVqG4JtaVfDU5FVQBU2wp3KpLFLOvram2z1H7yA7VODcb6K7vNMonamrdfr8WaVmuDWPzcMBcLxuqjp3JgHVoV2ifwFdPziUKI-7N-y6TiVkU512IAOakSpMK98I1jvpBt7_NCmi5zfDsCusXSPJ_bhNaQ55lW9T5DTaAGdWIgnYPgRQt9f-VBPV9QgsPs-d83UGGXAD50q-4l2w5OKzA5egDs5o64rxudplVyteJZD1xrhM43qr-Li9ewp-IdVpKb66tVuER6PVZamxrWpRTKxQg-vrQnmR1vvQfNy4zY3A0dfVgahggwPD4gaBhS_VAa3-PhVO5XyY9asVnuickrdgFS-iYUkBsYW1Bts3qnrt3E0GwyUWzXbTS6np_RGKZ5yEQLjTiYFEbIz5bAvPpqx5Xa35xhEesonrVBcAW0Gx_qGprxpxuJdPSZpEKWap33rWNaf6W2N0Pa3oBUJTbDyiEWfVJnxheQf715mrzzJKNKhWORtSRlQdNA_eFIFBz9XDyKdegkjrMytr1CQIptosBXb_5pL-GEu4Sb6N6S5ygSNclETyI5jqzt_PIkZFq0KKrpz5lZzKgJLe9DQi1v0f2qVZfxE6V_EC17emrsmPLQgw_nu-NJqVD1GciOXg", 503 | "type": "string" 504 | } 505 | ] 506 | }, 507 | "method": "GET", 508 | "header": [ 509 | { 510 | "warning": "This is a duplicate header and will be overridden by the Authorization header generated by Postman.", 511 | "key": "Authorization", 512 | "value": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6ImRhMjU0NTA3MDU3MWU4OTc0OGJiNjQ4Mzk0YWFmMmRlZTcwNjhhYWM1MTEwMmRiNTg4ZDM0YzUyZDRkNTgyOGM5ZGFhMTJkNTc1YmFlZjBlIn0.eyJhdWQiOiIyIiwianRpIjoiZGEyNTQ1MDcwNTcxZTg5NzQ4YmI2NDgzOTRhYWYyZGVlNzA2OGFhYzUxMTAyZGI1ODhkMzRjNTJkNGQ1ODI4YzlkYWExMmQ1NzViYWVmMGUiLCJpYXQiOjE1ODI5MTIxNDcsIm5iZiI6MTU4MjkxMjE0NywiZXhwIjoxNjE0NTM0NTQ3LCJzdWIiOiIxIiwic2NvcGVzIjpbIioiXX0.bbhcN2OLO3y_pVIfhtdiHxNI1myDGiObWC_5NND4duJkkGmUzZCTjHEI4pMKXIMJMnADBGulTxB9HybqaxN0rNiqE99X1_tJcwHt3OZwx32_gg9Sg9nGJv4mgRylkESJIW7j1aLwPZxQaPP_5P_0ZIYVaQKRMNRkrsaKcLXJPjK90Uqrj2uvoHorfG1-rKyJVo7iu-6fjojbR1-05yf0LASLphcQSoP2GUYSFNhTtweLosflaPo_rE8OUsvgBF7yE9PojVqf4xbt8ffzNUYZF5sr-jao72ZHkHxPdhNHTZknWvuSdCnDuJhTnRausNbK_PDawam3TGnEihZId3VhnnHL65MV6K4x_hmqYDoKr8u-X7yrSnFUh8xQejoCuDITsG5tN8BFUW1n_1_GJp5AkJxFsThMNfjUEV76BLdngFusxTPo5Ke0oW0nRhnhcR4cbpvNmIZ-Eb8WDx8L3Aj5XIH-dexV_brcPMCWgen7HCQ-7HS_XBDbdtehtkMMdxYvJu7Y4bpmm-JW7LxPQkMWy3eruw_S3ZGQAD3zvY2JmxaqARxFL2qqpBGsByEZo_SQPUZwwJGAey2WISR0eQiOOvQ2wJIax6Z8r8DBeV4DXPUJfOsYD22pByEtliopKMwdeKwRAxgD5EppaYdCpSrRv6P0PfpOiHqKRcrzdkd30L4", 513 | "type": "text", 514 | "disabled": true 515 | } 516 | ], 517 | "body": { 518 | "mode": "formdata", 519 | "formdata": [] 520 | }, 521 | "url": { 522 | "raw": "localhost/users/me", 523 | "host": [ 524 | "localhost" 525 | ], 526 | "path": [ 527 | "users", 528 | "me" 529 | ] 530 | } 531 | }, 532 | "response": [] 533 | } 534 | ], 535 | "protocolProfileBehavior": {} 536 | } --------------------------------------------------------------------------------