Use Lumen 7 + Api Resource for Api development

Writing in the front

Laravel has been used to develop API projects for several years. I found that every time I started a new API project, I would carry out some preprocessing based on Laravel, including the structure design for API projects, the encapsulation of unified response structure, the capture and processing of exceptions and the configuration of authorization modules. Always doing repetitive work, so encapsulate these common basics into a “startup template”.

Project address: Stamp here

Update the content

  • Specification of Message prompts in Reponse (2020-06-02)

Why Lumen?

Nowadays, medium and large projects are usually developed in the way of front and back end separation. The front and back end respectively maintain the project code through different code repositories, while Laravel is only responsible for the API part of the project and provides THE API to the front end call. In this scenario, using Laravel for the development API is a bit “bloated”.

Lumen, by contrast, streamlined much of Laravel for API development scenarios in the project. With Laravel experience, switching to Lumen was also easy.

An overview of

  • Adapt the HttpClient client added in Laravel 7
  • Use the Laravel native Api Resource
  • Standardize uniform response structures
  • Authorization in JWT-Auth mode
  • Logging to MongoDB is supported
  • Designing the Repository & Service Architecture efficiently (😏)

Canonical response structure

Abstract: RESTful Service best Practices

Code – Contains an HTTP response status code of type integer. Status — Contains text: “success”, “fail” or” error”. The HTTP status response codes between 500 and 599 are fail, and between 400 and 499 are Error, and other status codes are SUCCESS (for example, the response status codes are 1XX, 2XX, and 3XX). Message – Valid when the status values are “fail” and “error”, used to display error messages. Referring to the internationalization (IL8N) standard, it can contain either a message number or an encoding, either only, or both and separated by a delimiter. Data — Contains the body of the response. When the status value is “fail” or” error”, data only contains the cause of the error or the exception name.

instructions

The overall response structure is designed based on the preceding information, which strictly complies with RESTful design criteria and returns a reasonable HTTP status code.

Given that businesses often need to return different “business description processing results,” passing messages that match business scenarios is supported in all response structures.

  • data:
    • When querying a single piece of data, the object structure is returned directly to reduce the data hierarchy.
    • Returns an array structure when querying list data;
    • If the creation or update is successful, the modified data is returned. (You can also return an empty object without returning data.)
    • An empty object is returned on successful deletion
  • status:
    • Error: The HTTP status response code is between 400 and 599. For example, passing in wrong parameters, accessing non-existent data resources, and so on
    • Fail, the server fails and the HTTP status response code is between 500 and 599. For example, code syntax error, empty object call function, database connection failure, undefined index, etc
    • Success: the HTTP response status code is 1XX, 2XX, and 3XX, indicating that the service is successfully processed.
  • Message: describes the result of the processing of the request action performed; It can also support internationalization and switch based on actual business requirements.
  • Code: indicates the HTTP response status code. You can adjust the service operation code based on actual service requirements

Code implementation


      


namespace App\Http;

use Illuminate\Http\Resources\Json\JsonResource;
use  \Illuminate\Http\Response as HttpResponse;

class Response
{
    public function errorNotFound($message = 'Not Found')
    {
        $this->fail($message, HttpResponse::HTTP_NOT_FOUND);
    }

    / * * *@param  string  $message
     * @param  int  $code
     * @param  null  $data
     * @param  array  $header
     * @param  int  $options
     * @throws \Illuminate\Http\Exceptions\HttpResponseException
     */
    public function fail(string $message = ' ', int $code = HttpResponse::HTTP_INTERNAL_SERVER_ERROR, $data = null, array $header = [], int $options = 0)
    {
        $status = ($code >= 400 && $code <= 499)?'error' : 'fail'; $message = (! $message &&isset(HttpResponse::$statusTexts[$code])) ? HttpResponse::$statusTexts[$code] : 'Service error';

        response()->json([
            'status' => $status,
            'code' => $code,
            'message' => $message,// Error description
            'data' => (object) $data,// Error details
        ], $code, $header, $options)->throwResponse();
    }

    public function errorBadRequest($message = 'Bad Request')
    {
        $this->fail($message, HttpResponse::HTTP_BAD_REQUEST);
    }

    public function errorForbidden($message = 'Forbidden')
    {
        $this->fail($message, HttpResponse::HTTP_FORBIDDEN);
    }

    public function errorInternal($message = 'Internal Error')
    {
        $this->fail($message, HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
    }

    public function errorUnauthorized($message = 'Unauthorized')
    {
        $this->fail($message, HttpResponse::HTTP_UNAUTHORIZED);
    }

    public function errorMethodNotAllowed($message = 'Method Not Allowed')
    {
        $this->fail($message, HttpResponse::HTTP_METHOD_NOT_ALLOWED);
    }

    public function accepted($message = 'Accepted')
    {
        return $this->success(null, $message, HttpResponse::HTTP_ACCEPTED);
    }

    / * * *@param  JsonResource|array|null  $data
     * @param  string  $message
     * @param  int  $code
     * @param  array  $headers
     * @param  int  $option
     * @return \Illuminate\Http\JsonResponse|JsonResource
     */
    public function success($data, string $message = ' ', $code = HttpResponse::HTTP_OK, array $headers = [], $option = 0)
    { $message = (! $message &&isset(HttpResponse::$statusTexts[$code])) ? HttpResponse::$statusTexts[$code] : 'OK';
        $additionalData = [
            'status'= >'success'.'code' => $code,
            'message' => $message
        ];

        if ($data instanceof JsonResource) {
            return $data->additional($additionalData);
        }

        return response()->json(array_merge($additionalData, ['data'=> $data ? : (object) $data]), $code, $headers, $option); }/ * * *@param  JsonResource|array|null  $data
     * @param  string  $message
     * @param  string  $location
     * @return \Illuminate\Http\JsonResponse|JsonResource
     */
    public function created($data = null, $message = 'Created', string $location = ' ')
    {
        $response = $this->success($data, $message, HttpResponse::HTTP_CREATED);
        if ($location) {
            $response->header('Location', $location);
        }

        return $response;
    }

    public function noContent($message = 'No content')
    {
        return $this->success(null, $message, HttpResponse::HTTP_NO_CONTENT); }}Copy the code

use

Use \\App\\Traits\ Helpers to call the Response method encapsulated in \\App\ HTTP \Response where HTTP responses are needed.

It is usually used in the Controller layer to respond to the result of the business process, so the Helperstrait has been introduced in the \\App\\Http\\Controllers base class, which can be called directly in the Controller as follows:

// Operation success
$this->response->success($data,$message);
$this->response->created($data,$message);
$this->response->accepted($message);
$this->response->noContent($message);

// The operation fails or is abnormal
$this->response->fail($message);
$this->response->errorNotFound();
$this->response->errorBadRequest();
$this->response->errorForbidden();
$this->response->errorInternal();
$this->response->errorUnauthorized();
$this->response->errorMethodNotAllowed();
Copy the code

Response structure when the operation succeeds

  • Returns a single piece of data
{
    "data": {
        "nickname": "Jiannei"."email": "[email protected]"
    },
    "status": "success"."code": 200."message": "Success"
}
Copy the code
  • Return list data
{
    "data": [{"nickname": "Jiannei"."email": "[email protected]"
        },
        {
            "nickname": "Qian"."email": "[email protected]"
        },
        {
            "nickname": "Turbo"."email": "[email protected]"} / /... ] ."links": {
        "first": "http://lumen-api.test/users? page=1"."last": null."prev": null."next": null
    },
    "meta": {
        "current_page": 1."from": 1."path": "http://lumen-api.test/users"."per_page": 15."to": 13
    },
    "status": "success"."code": 200."message": "Success"
}
Copy the code

Response structure when an operation fails

{
    "status": "fail"."code": 500."message": "Service error"."data": {}}Copy the code

Response structure for exception catching

The overall format is the same as that when a service operation succeeds or fails. Compared with that when a service operation fails, additional exception information is displayed in the Data section to facilitate rapid fault location during project development.

  • Customization is implementedValidationExceptionResponse structure of
{
    "status": "error"."code": 422."message": "Validation error"."data": {
        "email": [
            "The email has already been taken."]."password": [
            "The password field is required."]}}Copy the code
  • NotFoundExceptionResponse structure for exception capture

When debugging is disabled:

{
    "status": "error"."code": 404."message": "Service error"."data": {
        "message": "No query results for model [App\\Models\\User] 19"}}Copy the code

When debugging is enabled:

{
    "status": "error"."code": 404."message": "Service error"."data": {
        "message": "No query results for model [App\\Models\\User] 19"."exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException"."file": "/var/www/lumen-api-starter/vendor/laravel/lumen-framework/src/Exceptions/Handler.php"."line": 107."trace": [{"file": "/var/www/lumen-api-starter/app/Exceptions/Handler.php"."line": 55."function": "render"."class": "Laravel\\Lumen\\Exceptions\\Handler"."type": "- >"
            },
            {
                "file": "/var/www/lumen-api-starter/vendor/laravel/lumen-framework/src/Routing/Pipeline.php"."line": 72."function": "render"."class": "App\\Exceptions\\Handler"."type": "- >"
            },
            {
                "file": "/var/www/lumen-api-starter/vendor/laravel/lumen-framework/src/Routing/Pipeline.php"."line": 50."function": "handleException"."class": "Laravel\\Lumen\\Routing\\Pipeline"."type": "- >"} / /... ] }}Copy the code
  • Response structure when other types of exceptions are caught
{
    "status": "fail"."code": 500."message": "syntax error, unexpected '$user' (T_VARIABLE)"."data": {
        "message": "syntax error, unexpected '$user' (T_VARIABLE)"."exception": "ParseError"."file": "/var/www/lumen-api-starter/app/Http/Controllers/UsersController.php"."line": 34."trace": [{"file": "/var/www/lumen-api-starter/vendor/composer/ClassLoader.php"."line": 322."function": "Composer\\Autoload\\includeFile"
            },
            {
                "function": "loadClass"."class": "Composer\\Autoload\\ClassLoader"."type": "- >"
            },
            {
                "function": "spl_autoload_call"} / /... ] }}Copy the code

Special note: X-requested-with should be added for use With Api testing tools like Postman: XMLHttpRequest or Accept: application/jsonheader information to show is the Api request, otherwise, the exception handling to return JSON response may not be expected.

Rich logging mode support

  • Logs (including service error logs and captured exception information) can be recorded to MongoDB to facilitate troubleshooting of online faults
  • Logs recorded to MongoDB can be divided by daily, monthly, and yearly tables
  • Supports recording SQL statements

Repository & Service pattern architecture

Andersao/L5-Repository was used for project structure design, and Service layer was added.

Job description

To be added.

specification

Naming conventions: To be added

Specification of use: to be added

Packages

  • guzzlehttp/guzzle
  • jenssegers/mongodb
  • tymon/jwt-auth
  • prettus/l5-repository
  • overtrue/laravel-query-logger

other

As usual, if you can help or inspire you in your daily work, please click on star + fork + Follow.

If you have any criticism or suggestions, you can reach me via email ([email protected]) (if I check email every day).

In a word, welcome all heroes and heroics.

reference

  • RESTful API best practices
  • RESTful Service best practices
  • DingoApi