• Introduction to the
  • Preparation before development
    • For the project
    • Version of the relevant
    • Environment set up
  • Create a project
  • API login
  • Unified Restful API response processing
  • Process the interface return value
  • There are three ways to implement enumeration
    • A single enumerated directory (one to one)
    • Configuration file (one-to-many)
    • Nested model (one to one)
  • Interface screen
  • Other configuration
    • Language pack Installation
    • Debug Debugging toolbar
    • laravel-ide-helper
    • Resolve cross-domain problems
  • conclusion

Introduction to the

I have encountered such a problem before. I usually maintain the existing projects or add some new functions. Occasionally, when I need to take charge of a project independently, I feel a little “at a loss”. You don’t know how to put the pieces together, and when you’re done, you’re like, “Gee, why didn’t I do that? I wasn’t thinking about it, I should have done it, etc.”

So, I thought, why not make a demo project that I can use or others can use? Because the requirements are different, this demo project encapsulates only some general and commonly used content. If there is something wrong, please click the correct button at the bottom right, and we will improve it together.

Preparation before development

Scope of Application

Suitable for small and medium-sized projects, not involving high concurrency, etc., just as a beginner to use.

The project focuses on the API interface part.

Version of the relevant

-barryvDH /laravel-cors: “^0.11.4”, -laravel /framework: “^6.2”, -laravel/Passport: “^ 8.0” – spatie/laravel – query – builder: “^ 2.3”

Environment set up

By default, you have installed the required LNMP environment, please see Laravel development environment deployment for environment setup

If you prefer an integrated environment, Laragon, PHPStudy are recommended

Create a project

Through the Laravel installer

composer global  require laravel/installer
Copy the code

Be sure to place Composer’s system-wide Vendor directory in your system environment variable $PATH. If ok, you can create a new project using the Laravel New project name.

Through the composer

Composer create-project --prefer-dist Laravel/Laravel project nameCopy the code

Pull projects from Github

You can pull this item directly by using the following command:

git clone https://github.com/Jouzeyu/api-demo.git
Copy the code

The related configuration

You can configure your database connection in the. Env file and then run the PHP artisan Migrate command to migrate the database.

API login

instructions

We use the Passport OAuth authentication as recommended, but you can also use the Dingo API. If your project is only used by a few people and you don’t need to refresh the token, you can also try Laravel API authentication.

The installation

composer require laravel/passport
Copy the code

Run the migration

php artisan migrate
Copy the code

After the migration, related tables will be automatically created in the database. If an error is displayed, please check whether the database connection in your. Env file is correct first.

Generate the secret key and client

Next, run the passport: Install command to create the encryption key needed to generate the secure access token, which also creates the “personal access” and “password authorization” clients used to generate the access token:

php artisan passport:install
Copy the code

Configuration and Use

Step 1: Reference HasApiTokens in the User model:

<? php namespace App; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use Notifiable, HasApiTokens; }Copy the code

In the second step: in the app/will/AuthServiceProvider boot method invokes the Passport: : routes.

<? php namespace App\Providers; use Laravel\Passport\Passport; use Illuminate\Support\Facades\Gate; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { /** * The policy mappingsfor the application.
     *
     * @var array
     */
    protected $policies = [
        //'App\Model'= >'App\Policies\ModelPolicy'
    ];

    /**
     * Register any authentication / authorization services.
     *
     * @return void
     */
    public function boot()
    {
        $this->registerPolicies(); Passport::routes(); }}Copy the code

Final step: In the config/auth.php configuration file, replace the API driver option with passport:

'guards'= > ['web'= > ['driver'= >'session'.'provider'= >'users',].'api'= > ['driver'= >'passport'.'provider'= >'users',]],Copy the code

Recommended article: Use Laravel Passport processing API authentication

Project related: Complete API login (other parts)

1. Create controller.php under app/Http/Controllers/Api and say:

<? php namespace App\Http\Controllers\Api; use Illuminate\Routing\Controller as BaseController; class Controller extends BaseController { }Copy the code

2. Create the Auth controller and modify it as follows:

<? php namespace App\Http\Controllers\Api; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; use Carbon\Carbon; use App\User; use Illuminate\Support\Facades\Hash; class AuthController extends Controller { /** * Create user * * @param [string] name * @param [string] email * @param [string] password * @return [string] message
     */
    public function register(Request $request)
    {
        $request->validate([
            'name'= >'required|string'.'email'= >'required|string|email|unique:users'.'password'= >'required|string'
        ]);

        $user = new User([
            'name'= >$request->name,
            'email'= >$request->email,
            'password' => Hash::make($request->password)
        ]);

        $user->save();

        return response()->json([
            'message'= >'User created successfully'
        ], 201);
    }

    /**
     * Login user and create token
     *
     * @param  [string] email
     * @param  [string] password
     * @param  [boolean] remember_me
     * @return [string] access_token
     * @return [string] token_type
     * @return [string] expires_at
     */
    public function login(Request $request)
    {
        $request->validate([
            'email'= >'required|string|email'.'password'= >'required|string'.'remember_me'= >'boolean'
        ]);

        $credentials = request(['email'.'password']);

        if(! Auth::attempt($credentials))
            return response()->json([
                'message'= >'Wrong username or password'
            ], 401);

        $user = $request->user();

        $tokenResult = $user->createToken('Personal Access Token');
        $token = $tokenResult->token;

        if ($request->remember_me)
            $token->expires_at = Carbon::now()->addWeeks(1);

        $token->save();

        return response()->json([
            'access_token'= >$tokenResult->accessToken,
            'token_type'= >'Bearer'.'expires_at' => Carbon::parse(
                $tokenResult->token->expires_at
            )->toDateTimeString()
        ]);
    }

    /**
     * Get the authenticated User
     *
     * @return [json] user object
     */
    public function userInfo(Request $request)
    {
        return response()->json($request->user()); }}Copy the code

3. API routing:

Route::group([
    'prefix'= >'v1'].function () {
    Route::post('login'.'Api\AuthController@login');
    Route::post('register'.'Api\AuthController@register');

    Route::group([
      'middleware'= >'auth:api'].function() {
        Route::get('user_info'.'Api\AuthController@userInfo');
    });
});
Copy the code

Project related: test results

Registration Result:

Login Result:

Replacement details result:

Unified Restful API response processing

instructions

I had a private chat with the front end of the company about what kind of Restful API response would be more friendly for them to call. The front end said that it would be good if only unified. However, after trying and docking, I found that the back end capturing all exceptions and adding them to the returned data was more suitable for the front end separation project. So that’s what we’re doing here.

Encapsulate the unified return information

We’ll create a new Helpers folder in our app/Api folder and create a file called apiResponse.php to store the content we want to encapsulate:

<? php namespace App\Api\Helpers; use Symfony\Component\HttpFoundation\Response as FoundationResponse; use Response; trait ApiResponse { /** * @var int */ protected$statusCode= FoundationResponse::HTTP_OK; / * * * @return mixed
     */
    public function getStatusCode()
    {
        return $this->statusCode;
    }

    /**
     * @param $statusCode
     * @return $this
     */
    public function setStatusCode($statusCode.$httpCode=null)
    {
        $httpCode = $httpCode ?? $statusCode;
        $this->statusCode = $statusCode;
        return $this;
    }

    /**
     * @param $data
     * @param array $header
     * @return mixed
     */
    public function respond($data.$header = [])
    {

        return Response::json($data.$this->getStatusCode(),$header);
    }

    /**
     * @param $status
     * @param array $data
     * @param null $code
     * @return mixed
     */
    public function status($status, array $data.$code = null){

        if ($code) {$this->setStatusCode($code);
        }
        $status = [
            'status'= >$status.'code'= >$this->statusCode
        ];

        $data = array_merge($status.$data);
        return $this->respond($data);

    }

    /**
     * @param $message
     * @param int $code
     * @param string $status
     * @returnMixed */ /* * format * data: * code:422 * message: XXX * status:'error'
     */
    public function error($message.$code = FoundationResponse::HTTP_BAD_REQUEST,$status = 'error') {return $this->setStatusCode($code)->message($message.$status);
    }

    /**
     * @param $message
     * @param string $status
     * @return mixed
     */
    public function message($message.$status = "success") {return $this->status($status['message'= >$message
        ]);
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function internalError($message = "Internal Error!") {return $this->error($message,FoundationResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function created($message = "created")
    {
        return $this->setStatusCode(FoundationResponse::HTTP_CREATED)
            ->message($message);

    }

    /**
     * @param $data
     * @param string $status
     * @return mixed
     */
    public function success($data.$status = "success") {return $this->status($status,compact('data'));
    }

    /**
     * @param string $message
     * @return mixed
     */
    public function notFond($message = 'Not Fond! ')
    {
        return $this->error($message,Foundationresponse::HTTP_NOT_FOUND); }}Copy the code

How to use

Modify the base class

<? php namespace App\Http\Controllers\Api; use App\Api\Helpers\ApiResponse; use App\Http\Controllers\Controller as BaseController; class Controller extends BaseController { use ApiResponse; // Reference the encapsulated error template}Copy the code

Other controller calls

1. Return the correct resource information

return $this->success($users);
Copy the code

2. The correct information about the customized HTTP status code is displayed

return $this->setStatusCode(201)->success($users);
Copy the code

3. An error message is displayed

return $this->error('User registration failed');
Copy the code

5. Error information about the user-defined HTTP status code is displayed

return $this->error('User login failed', 401);Copy the code

Disclaimer: This section is a manual guide to making Laravel API development more handy

Project related: improve other parts

1. Create a Users controller to facilitate testing:

<? php namespace App\Http\Controllers\Api; use App\User; use Illuminate\Http\Request; class UsersController extends Controller { publicfunction test(Request $request) {$user=User::where('id',1)->first();
       return $this->success($user); }}Copy the code

2. Register a route

Route::group([
    'prefix'= >'v1'].function () {
    Route::get('test'.'Api\UsersController@test'); // No login authentication is required... });Copy the code

Project related: test results

Process the interface return value

Problems thrown

We limit the return value for security reasons and so on. As you can see, when User information is returned in the previous section, all data is returned, including sensitive information password, common sensitive information also includes wechat OpenID, etc. How to avoid this section is the main content.

API resources

Introduction to the

You often need a transformation layer to connect your Eloquent model to the ACTUAL JSON response that is returned to the user, known as the ViewModel layer.

Creating user Resources

php artisan make:resource Api/UserResource
Copy the code

PHP: userResources.php: userresources.php: userresources.php: userresources.php: userresources.php: userresources.php: userresources.php

<? php namespace App\Http\Resources\Api; use Illuminate\Http\Resources\Json\JsonResource; Class UserResource extends JsonResource {/** * this specifies the return value ** @param \Illuminate\Http\Request$request
     * @return array
     */
    public function toArray($request)
    {
        return [
            'id'= >$this->id,
            'name'= >$this->name,
			'email'= >$this->email,
            'created_at'=>(string)$this->created_at,
            'updated_at'=>(string)$this->updated_at ]; }}Copy the code

How to use

To open our User test controller, we will:

  return $this->success($user);
Copy the code

Replace with:

return $this->success(new UserResource($user));
Copy the code

Note: This method is only suitable for returning a single user, otherwise paging will not work. For a list of users, you can use return UserResource:: Collection ($user);

Project related: test results

There are three ways to implement enumeration

Now there is a requirement to add state to the user, such as freeze, normal. This is a very common requirement, we usually add a status field in the data table, and use a number to represent identity, but we know what the number means for a short time, and then we forget it, and the front end people don’t know. So what do we do? We use enumerations.

A separate enumerated directory

Creating an Enum directory

Create an Enum directory under app to store our enumeration files, such as userenum.php:

<? php namespace App\Enum; Class UserEnum {// State class const NORMAL = 1; // Normal const FREEZE = 2; // Freeze public staticfunction getStatusName($status){
        switch ($status) {case self::NORMAL:
                return 'normal';
            case self::FREEZE:
                return 'freeze';
            default:
                return 'normal'; }}}Copy the code

use

Modify UserResource. PHP:

return [
            'id'= >$this->id,
            'name'= >$this->name,
            'email'= >$this->email,
			'status'  =>  UserEnum::getStatusName($this->status),
            'created_at'=>(string)$this->created_at,
            'updated_at'=>(string)$this->updated_at
        ];
Copy the code

Tip: frozen state should not be able to log in, here the judgment logic please write their own

The configuration file

Because it’s a little bit longer, you can look at my previous blog and see how to gracefully use helpers.php, right

Nested too in the model

In the same way, if you look at my previous blog, you can protect the type field and so on.

Enumeration is not included in the demo project because everyone likes it differently.

Interface screen

instructions

We are almost there, but we still need one important module, which is interface filtering. The goal is to get the corresponding filtered data through different parameters in the front end. We use the Laravel-Query-Builder extension package here.

The installation

composer require spatie/laravel-query-builder
Copy the code

Release configuration

php artisan vendor:publish --provider="Spatie\QueryBuilder\QueryBuilderServiceProvider" --tag="config"
Copy the code

How to use

1. First make sure the required fields are allowed in the model, for example in the User model:

 protected $fillable = [
        'name'.'email'.'password',];Copy the code

2. Used in the controller:

<? php namespace App\Http\Controllers\Api; use App\Http\Resources\Api\UserResource; use App\User; use Illuminate\Http\Request; use Spatie\QueryBuilder\QueryBuilder; class UsersController extends Controller { publicfunction test(Request $request) {$users = QueryBuilder::for(User::class)
           ->allowedFilters(['name'])
           ->get();
       return UserResource::collection($users); }}Copy the code

3. Front-end screening

http://api-demo.test/api/v1/test? filter[name]=jouzeyuCopy the code

Use Spatie\QueryBuilder\QueryBuilder; And not the other way around. We use allowedFilters to declare fields that are allowed to be filtered. This is just a preliminary introduction, please see the documentation for more information

Other configuration

Language pack Installation

Composer require caouecs/laravel - lang: ~ 4.0Copy the code

Debug Debugging toolbar

composer require barryvdh/laravel-debugbar --dev
Copy the code

Tip: –dev means install locally only

laravel-ide-helper

composer require --dev barryvdh/laravel-ide-helper
Copy the code

Resolve cross-domain problems

The installation

composer require barryvdh/laravel-cors
Copy the code

configuration

Add to app/Http/ kernel.php:

protected $middleware = [
    // ...
    \Barryvdh\Cors\HandleCors::class,
];
Copy the code

conclusion

The demo project is over for the time being, maybe he is not perfect, but I still hope to inspire friends who have just started. If you have a better idea, feel free to comment below. Thanks.

Open source: github.com/Jouzeyu/api…