fcn从头开始_从头开始构建REST API-简介

fcn从头开始

The current internet ecosystem has literally been invaded by APIs, and for good reasons. By using third party APIs in your products or services, you have access to a ton of useful features — such as authentication or storage services — that can benefit both you and your users. By exposing your own API, your application becomes “part of the mix” and will be used in ways you’ve never thought before… if you do it the right way, obviously.

API确实入侵了当前的互联网生态系统,这是有充分理由的。 通过在产品或服务中使用第三方API,您可以访问大量有用的功能(例如身份验证或存储服务),这些功能可以使您和您的用户受益。 通过公开自己的API,您的应用程序将成为“组合的一部分”,并且将以您从未想过的方式使用它……显然,如果您以正确的方式进行操作。

In this two part series I’ll show you how to create a RESTful API layer for your PHP applications, using a collection of real world best practices.

在这个由两部分组成的系列文章中,我将向您展示如何使用现实世界中的最佳实践为您PHP应用程序创建RESTful API层。

The full source code of this project will be available at the end of part 2.

该项目的完整源代码将在第2部分末尾提供。

REST:开发人员愉快的用户界面 (REST: A pleasant UI for developers)

First of all, an API is a user interface for developers, so it must be friendly, simple, easy to use and of course pleasant; or else it will end up being another piece of digital junk out there.

首先,API是开发人员的用户界面 ,因此它必须友好,简单,易于使用并且当然令人愉悦; 否则最终将成为那里的另一个数字垃圾。

Documentation, even in the form of a simple but well written README file, is a good place to start. The minimal information we need is a summary of the service’s scope and the list of methods and access points.

即使是简单但写得很好的README文件形式的文档,也是一个很好的起点。 我们需要的最少信息是服务范围以及方法和访问点列表的摘要。

A good summary can be:

一个很好的总结可以是:

Our application is a simple contact list service that manages contacts with linked notes. It has two object types, contacts and notes. Each contact has basic attributes such as first name, last name, and email address. Also, each contact can have a number of markdown-formatted notes linked to it.

我们的应用程序是一个简单的联系人列表服务,用于管理带有链接注释的联系人。 它具有两种对象类型 ,即联系人和便笺。 每个联系人都具有基本属性,例如名字,姓氏和电子邮件地址。 同样,每个联系人可以链接到许多降价格式的注释。

Then, it’s a good idea to make a list of all the resources and actions that we are going to implement. This can be seen as the equivalent of wireframing for visual applications. Following the key principles of REST, each resource is represented by a URL, where the action is the HTTP method used to access it.

然后,最好列出所有要实施的资源和操作 。 可以将其视为可视化应用程序中的线框图。 遵循REST的关键原理,每个资源都由URL表示,其中操作是用于访问它的HTTP方法。

For example GET /api/contacts/12 retrieves the contact with id of 12, while PUT /api/contacts/12 will update that same contact.

例如GET /api/contacts/12检索id为12的联系人,而PUT /api/contacts/12将更新该联系人。

The full list of methods is displayed below:

方法的完整列表显示在下面:

URL HTTP Method Operation
/api/contacts GET Returns an array of contacts
/api/contacts/:id GET Returns the contact with id of :id
/api/contacts POST Adds a new contact and return it with an id attribute added
/api/contacts/:id PUT Updates the contact with id of :id
/api/contacts/:id PATCH Partially updates the contact with id of :id
/api/contacts/:id DELETE Deletes the contact with id of :id

/api/contacts/:id/star PUT Adds to favorites the contact with id of :id
/api/contacts/:id/star DELETE Removes from favorites the contact with id of :id

/api/contacts/:id/notes GET Returns the notes for the contact with id of :id
/api/contacts/:id/notes/:nid GET Returns the note with id of :nid for the contact with id of :id
/api/contacts/:id/notes POST Adds a new note for the contact with id of :id
/api/contacts/:id/notes/:nid PUT Updates the note with id if :nid for the contact with id of :id
/api/contacts/:id/notes/:nid PATCH Partially updates the note with id of :nid for the contact with id of :id
/api/contacts/:id/notes/:nid DELETE Deletes the note with id of :nid for the contact with id of :id

For a more complete and professional documentation, you may consider some tools like Swagger, apiDoc or Google APIs Discovery Service: your users will love you!

有关更完整和专业的文档,您可以考虑使用SwaggerapiDocGoogle APIs Discovery Service等工具 :您的用户会爱上您!

工具和设置 (Tools and setup)

The main tool I’m going to use to build our API is Slim Framework. Why?

我将用来构建API的主要工具是Slim Framework 。 为什么?

[It] helps you quickly write simple yet powerful web applications and APIs.

[It]帮助您快速编写简单但功能强大的Web应用程序和API。

And that’s true. Its powerful routing feature makes it easy to use methods other than the standard GET and POST, it provides built-in support for HTTP method overridess (via both HTTP header and hidden POST fields) and it can be hooked with middleware and extra features to make apps and API development really easy.

没错。 它强大的路由功能使除标准GETPOST之外的方法易于使用,它对HTTP方法覆盖提供了内置支持(通过HTTP标头和隐藏的POST字段),并且可以与中间件和其他附加功能挂钩。应用和API开发真的很容易。

Along with Slim I’m using Idiorm to access the database layer and Monolog for logging. So our composer.json file will look like this:

与Slim一起,我使用Idiorm访问数据库层,并使用Monolog进行日志记录。 因此,我们的composer.json文件将如下所示:

{
"name": "yourname/my-contacts",
"description": "Simple RESTful API for contacts management",
"license": "MIT",
"authors": [
{
"name": "Your Name",
"email": "you@yourdomain.com"
}
],
"require": {
"slim/slim": "*",
"slim/extras": "*",
"slim/middleware": "*",
"monolog/monolog": "*",
"j4mie/paris": "*",
"flynsarmy/slim-monolog": "*"
},
"archive": {
"exclude": ["vendor", ".DS_Store", "*.log"]
},
"autoload": {
"psr-0": {
"API": "lib/"
}
}
}

The slim/extras and slim/middleware packages provide useful features such as content type parsing and basic authentication. Our custom classes are under the namespace of API and sit inside the lib directory.

slim/extrasslim/middleware软件包提供了有用的功能,例如内容类型解析和基本身份验证。 我们的自定义类位于API的命名空间下,位于lib目录中。

At this point our working directory structure would look like this:

此时,我们的工作目录结构将如下所示:

bootstrap.php
composer.json
README.md
bin/
    import
    install
lib/
    API/
public/
    .htaccess
    index.php
share/
    config/
        default.php
    db/
    logs/
    sql/
        data/
            contacts.sql
            users.sql
        tables/
            contacts.sql
            notes.sql
            users.sql
        ssl/
            mysitename.crt
            mysitename.key

The front controller of our application is public/index.php, where all the non-file-or-directory traffic is redirected via standard URL rewrite rules.

我们应用程序的前端控制器是public/index.php ,其中所有非文件或目录流量都通过标准URL重写规则进行重定向。

I’ve then placed all the initialization code in bootstrap.php which we’ll see later. The share directory contains data such as logs, configuration files, the SQLite databases and dump files, and the SSL certificates. The bin directory contains utility scripts that create the database and import some data using the provided .sql files.

然后,我将所有初始化代码放在bootstrap.php ,我们将在后面看到。 share目录包含诸如日志,配置文件,SQLite数据库和转储文件以及SSL证书之类的数据。 bin目录包含实用程序脚本,这些实用程序脚本创建数据库并使用提供的.sql文件导入一些数据。

SSL无处不在 (SSL Everywhere)

Our API will be accessible only in HTTPS mode, with no redirection. This simplifies the authentication logic and prevents poorly configured clients to access non encrypted endpoints. The easiest and more logical way to set this up is acting directly on the web server or through a proxy server.

我们的API仅可在HTTPS模式下访问,而无需重定向。 这简化了身份验证逻辑,并防止配置错误的客户端访问未加密的端点。 进行此设置的最简单,更合乎逻辑的方法是直接在Web服务器上或通过代理服务器进行操作。

I’m using old trusted Apache to do this, and my the virtual host file looks like this:

我正在使用旧的受信任的Apache来执行此操作,并且我的虚拟主机文件如下所示:

<Directory "/path/to/MyApp/public">

# Required for mod_rewrite in .htaccess
AllowOverride FileInfo

Options All -Indexes

DirectoryIndex index.php index.shtml index.html

<IfModule php5_module>
# For Development only!
php_flag display_errors On
</IfModule>

# Enable gzip compression
<ifModule filter_module>
AddOutputFilterByType DEFLATE application/json
</ifModule>

Order deny,allow
Deny from all
Allow from 127.0.0.1
</Directory>

<VirtualHost *:80>
ServerAdmin you@yourdomain.com
DocumentRoot "/path/to/MyApp/public"
ServerName myapp.dev

<IfModule rewrite_module>
RewriteEngine on

## Throw a 403 (forbidden) status for non secure requests
RewriteCond %{HTTPS} off
RewriteRule ^.*$ - [L,R=403]
</IfModule>

</VirtualHost>

<IfModule ssl_module>

NameVirtualHost *:443

Listen 443
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin

<VirtualHost *:443>
ServerAdmin you@yourdomain.com
DocumentRoot "/path/to/MyApp/public"
ServerName myapp.dev

SSLEngine on
SSLCertificateFile /path/to/MyApp/share/ssl/mysitename.crt
SSLCertificateKeyFile /path/to/MyApp/share/ssl/mysitename.key

SetEnv SLIM_MODE development

</VirtualHost>
</IfModule>

Directory settings are defined first, so that they are in common with both the HTTP and HTTPS version of our site. In the non-secure host configuration I’m using mod_rewrite to issue a 403 Forbidden error for any non secure connection, then in the secure section I’m setting up SSL using my self-signed certificates, along with the SLIM_ENV variable that tells Slim the current application mode.

首先定义目录设置,以便它们与我们站点的HTTP和HTTPS版本相同。 在非安全主机配置中,我使用mod_rewrite为任何非安全连接发出403 Forbidden错误,然后在安全部分中,使用我的自签名证书以及告知Slim的SLIM_ENV变量来设置SSL。当前的应用模式。

For more information on how to create a self signed certificate an install it on your Apache see this article on SSLShopper.

有关如何创建自签名证书并将其安装在Apache上的更多信息,请参见SSLShopper上的此文章

Now that we have clear objectives, a basic directory structure, and a server setup, let’s run a composer.phar install and start writing some code.

现在我们有了明确的目标,基本的目录结构和服务器设置,让我们运行composer.phar install并开始编写一些代码。

引导程序和前端控制器 (Bootstrap and Front Controller)

As said before, the bootstrap.php file is responsible for loading our application settings and autoloader setup.

如前所述, bootstrap.php文件负责加载我们的应用程序设置和自动加载器设置。

// Init application mode
if (empty($_ENV['SLIM_MODE'])) {
$_ENV['SLIM_MODE'] = (getenv('SLIM_MODE'))
? getenv('SLIM_MODE') : 'development';
}

// Init and load configuration
$config = array();

$configFile = dirname(__FILE__) . '/share/config/'
. $_ENV['SLIM_MODE'] . '.php';

if (is_readable($configFile)) {
require_once $configFile;
} else {
require_once dirname(__FILE__) . '/share/config/default.php';
}

// Create Application
$app = new API\Application($config['app']);

First I get the current environment. If a file named <EnvName>.php is present it’s loaded, if not the default config file is loaded. Slim specific settings are stored in the $config['app'] array and passed to the constructor of our application that extends the basic Slim object (optional but recommended).

首先,我了解当前的环境。 如果存在名为<EnvName>.php ,则将其加载,否则,将加载默认配置文件。 特定于Slim的设置存储在$config['app']数组中,并传递给扩展基本Slim对象的应用程序的构造函数(可选,但建议)。

For example the statement:

例如,语句:

$config['app']['log.writer'] = new \Flynsarmy\SlimMonolog\Log\MonologWriter(array(
'handlers' => array(
new \Monolog\Handler\StreamHandler(
realpath(__DIR__ . '/../logs')
.'/'.$_ENV['SLIM_MODE'] . '_' .date('Y-m-d').'.log'
),
),
));

configures a Monolog logger that writes to a file at app/path/share/logs/EnvName_YYYY-mm-dd.log.

配置Monolog记录器,该记录器写入app/path/share/logs/EnvName_YYYY-mm-dd.log

Then, after some refinements (you can see them in the source), I get the generated log writer and try to connect to the database:

然后,经过一些改进(您可以在源代码中看到它们),我得到了生成的日志编写器,并尝试连接到数据库:

// Get log writer
$log = $app->getLog();

// Init database
try {

if (!empty($config['db'])) {
\ORM::configure($config['db']['dsn']);
if (!empty($config['db']['username']) && !empty($config['db']['password'])) {
\ORM::configure('username', $config['db']['username']);
\ORM::configure('password', $config['db']['password']);
}
}

} catch (\PDOException $e) {
$log->error($e->getMessage());
}

// Add middleware
// Your middleware here...
$app->add(new Some\Middleware\Class(...));

Lastly I add the needed middleware to my application instance. Slim’s middleware is like the layers of an onion, the first middleware you add will be the innermost layer, so the order of our middleware is important. I’m using the following middleware in our API:

最后,我将所需的中间件添加到我的应用程序实例中。 Slim的中间件就像洋葱的层,您添加的第一个中间件将是最里面的一层,因此我们的中间件的顺序很重要。 我在API中使用了以下中间件:

  • Cache (the innermost);

    缓存(最里面);
  • ContentTypes: parses JSON-formatted body from the client;

    ContentTypes:从客户端解析JSON格式的主体;
  • RateLimit: manages users API limits;

    RateLimit:管理用户的API限制;
  • JSON: utility middleware for “JSON only responses” and “JSON encoded bodies” best practices;

    JSON:实用工具中间件,用于“仅JSON响应”和“ JSON编码的主体”最佳实践;
  • Authentication (outermost).

    身份验证(最外层)。

We will write all these except for the pre-existing ContentTypes.

我们将编写所有这些内容,除了预先存在的ContentTypes

At the end of the bootstrap file I’ve defined the two global variables $app (the application instance) and $log (the log writer). The file is loaded by our front controller index.php where the magic happens.

在引导文件的末尾,我定义了两个全局变量$app (应用程序实例)和$log (日志编写器)。 该文件由发生魔术的前端控制器index.php加载。

路由结构 (Routing structure)

Slim has a nice feature called Route Groups. With this feature we can define our application routes like this:

Slim具有一个不错的功能,称为Route Groups 。 使用此功能,我们可以像下面这样定义应用程序路由:

// API group
$app->group('/api', function () use ($app, $log) {

// Version group
$app->group('/v1', function () use ($app, $log) {

// GET route
$app->get('/contacts/:id', function ($id) use ($app, $log) {

});

// PUT route, for updating
$app->put('/contacts/:id', function ($id) use ($app, $log) {

});

// DELETE route
$app->delete('/contacts/:id', function ($id) use ($app, $log) {

});

});

});

I’ve created two nested groups, /api and /v1, so we can easily adhere to the “versioning in the URL” best practice. I’ve also created some optional routes for /api/, that could contain user-readable content, and a generic root URL (/) URL that in the real world could contain the public user interface for the app.

我创建了两个嵌套组/api/v1 ,因此我们可以轻松遵循“ URL版本”的最佳实践。 我还为/api/创建了一些可选的路由,其中​​可能包含用户可读的内容,以及在现实世界中可能包含应用程序公共用户界面的通用根URL( / )URL。

JSON中间件 (JSON middleware)

My initial approach here was using a route middleware (another type of Slim’s middleware) inside the /v1 group for authentication and JSON requests/responses, but I’ve found it is more practical and clean to use classic middleware. As seen previously, a middleware is an instance of a class that inherits from Slim\Middleware. The call() method of a Slim middleware is where the action takes place, it’s executed automatically when the middleware is linked as global, with the $app->add() method.

我最初的方法是在/v1组中使用路由中间件(Slim的另一种中间件)进行身份验证和JSON请求/响应,但是我发现使用经典中间件更加实用和简洁。 如前所述,中间件是从Slim\Middleware继承的类的实例。 Slim中间件的call()方法是执行操作的地方,当中间件通过$app->add()方法作为全局链接时,它将自动执行。

$app->add(new API\Middleware\JSON('/api/v1'));

Our JSON middleware achieves two best practices: “JSON only responses” and “JSON encoded bodies”. Here’s how:

我们的JSON中间件实现了两个最佳实践:“仅JSON响应”和“ JSON编码的主体”。 这是如何做:

// JSON middleware call
public function call()
{
if (preg_match('|^' . $this->root . '.*|', $this->app->request->getResourceUri())) {

// Force response headers to JSON
$this->app->response->headers->set(
'Content-Type',
'application/json'
);

$method = strtolower($this->app->request->getMethod());
$mediaType = $this->app->request->getMediaType();

if (in_array(
$method,
array('post', 'put', 'patch')
)) {

if (empty($mediaType)
|| $mediaType !== 'application/json') {
$this->app->halt(415);
}
}
}
$this->next->call();
}

We can pass a root path to our middleware constructor. In this case I’m passing /api/v1 so that our middleware is applied only on the API part of our site. If the current path matches the response content type header is forced to application/json, then I check the request method. If the request method is one of the write-enabled ones (PUT, POST, PATCH) the request content type header must be application/json, if not the application exits with a 415 Unsupported Media Type HTTP status code.

我们可以将根路径传递给中间件构造函数。 在这种情况下,我传递了/api/v1因此我们的中间件仅应用于我们网站的API部分。 如果当前路径匹配,则响应内容类型标头被强制为application/json ,那么我将检查请求方法。 如果请求方法是启用写操作的方法之一( PUTPOSTPATCH ),则请求内容类型标头必须为application/json ,否则,应用程序将以415 Unsupported Media Type HTTP状态代码退出。

If all is right the statement $this->next->call() runs the next middleware in the chain.

如果一切正确,则语句$this->next->call()将运行链中的下一个中间件。

认证方式 (Authentication)

Since our application will run on HTTPS by default, I decided to use the token-over-basic-authentication approach: an API key is sent in the username field of the basic HTTP AUTH headers (no password required). In order to do this I wrote a Slim middleware class called TokenOverBasicAuth, by modifying the existing Slim HttpBasicAuth. This middleware is run first in the chain so it is added as last, and it takes an optional root path parameter in the constructor.

由于我们的应用程序默认情况下将在HTTPS上运行,因此我决定使用token-over-basic-authentication方法:在基本HTTP AUTH标头的用户名字段中发送API密钥(无需密码)。 为此,我通过修改现有的Slim HttpBasicAuth编写了一个名为TokenOverBasicAuth的Slim中间件类。 该中间件在链中首先运行,因此最后添加,并且在构造函数中采用了可选的根路径参数。

// Auth middleware call
public function call()
{
$req = $this->app->request();
$res = $this->app->response();

if (preg_match('|^' . $this->config['root'] . '.*|', $req->getResourceUri())) {

// We just need the user
$authToken = $req->headers('PHP_AUTH_USER');

if (!($authToken && $this->verify($authToken))) {
$res->status(401);
$res->header(
'WWW-Authenticate',
sprintf('Basic realm="%s"', $this->config['realm'])
);
}

}

$this->next->call();
}

The method searches for the auth token within the PHP_AUTH_USER request header, if it does not exist or is invalid a 401 Forbidden status and authentication headers are passed to the client. The verify() method is protected so that it can be overridden by child classes; my version here is simple:

如果该方法不存在或无效,则该方法在PHP_AUTH_USER请求标头中搜索auth令牌,并且将401 Forbidden状态和身份验证标头传递给客户端。 verify()方法是受保护的,以便可以被子类覆盖; 我的版本很简单:

protected function verify($authToken)
{
$user = \ORM::forTable('users')->where('apikey', $authToken)
->findOne();

if (false !== $user) {
$this->app->user = $user->asArray();
return true;
}

return false;
}

Here I’m simply checking for the presence of the API key in the users table, and If I find a valid user it’s added to the application context to be used with the next layer (RateLimit). You can modify or extend this class to inject your own authentication logic or use an OAuth module. For more info on OAuth see Jamie Munro’s article.

在这里,我只是检查users表中是否存在API密钥,如果找到有效用户,则将其添加到应用程序上下文中,以与下一层(RateLimit)一起使用。 您可以修改或扩展此类以注入您自己的身份验证逻辑或使用OAuth模块 。 有关OAuth的更多信息,请参见Jamie Munro的文章

消耗性错误负载 (Consumable error payloads)

Our API should show useful error messages in consumable format, if possible in JSON representation. We need a minimal payload that contains an error code and message. In addition, validation errors require more breakdown. With Slim we can redefine both 404 errors and server errors with the $app->notFound() and $app->error() method respectively.

如果可能,我们的API应该以消耗性格式显示有用的错误消息,并以JSON表示形式显示。 我们需要一个包含错误代码和消息的最小有效负载。 此外,验证错误还需要更多细分。 使用Slim,我们可以分别使用$app->notFound()$app->error()方法重新定义404错误和服务器错误。

$app->notFound(function () use ($app) {

$mediaType = $app->request->getMediaType();

$isAPI = (bool) preg_match('|^/api/v.*$|', $app->request->getPath());

if ('application/json' === $mediaType || true === $isAPI) {

$app->response->headers->set(
'Content-Type',
'application/json'
);

echo json_encode(
array(
'code' => 404,
'message' => 'Not found'
)
);

} else {
echo '<html>
<head><title>404 Page Not Found</title></head>
<body><h1>404 Page Not Found</h1><p>The page you are
looking for could not be found.</p></body></html>';
}
});

Not found errors are simpler: first I’m grabbing the media type of the request, then the $isAPI flag tells me if the current URL is under the /api/v* group. If the client requested an API URL or sent a JSON content type header I’m returning a JSON output, else I can render a template or simply print some static HTML, as in this example.

未发现错误更简单:首先,我获取请求的媒体类型,然后$isAPI标志告诉我当前URL是否在/api/v*组下。 如果客户端请求API URL或发送JSON内容类型标头,则我将返回JSON输出,否则,我可以呈现模板或仅打印一些静态HTML,如本例所示。

Other errors are a little bit tricky, the $app->error() method is triggered when there is an exception and Slim transforms standard PHP errors in ErrorException objects. We need a way to give a useful error to the client without exposing too much of our internal mechanism in order to avoid security flaws. I’ve created two custom exceptions for this application, an API\Exception and an API\Exception\ValidationException that are exposed to the public, all other exception types are logged, and displayed only in development mode.

其他错误有些棘手,当出现异常时会触发$app->error()方法,并且Slim在ErrorException对象中转换标准PHP错误。 我们需要一种在不暴露过多内部机制的情况下向客户端提供有用错误的方法,以避免安全漏洞。 我为此应用程序创建了两个自定义异常,一个API\Exception和一个API\Exception\ValidationException公开,所有其他异常类型都被记录下来,并且仅在开发模式下显示。

$app->error(function (\Exception $e) use ($app, $log) {

$mediaType = $app->request->getMediaType();

$isAPI = (bool) preg_match('|^/api/v.*$|', $app->request->getPath());

// Standard exception data
$error = array(
'code' => $e->getCode(),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
);

// Graceful error data for production mode
if (!in_array(
get_class($e),
array('API\\Exception', 'API\\Exception\ValidationException')
)
&& 'production' === $app->config('mode')) {
$error['message'] = 'There was an internal error';
unset($error['file'], $error['line']);
}

// Custom error data (e.g. Validations)
if (method_exists($e, 'getData')) {
$errors = $e->getData();
}

if (!empty($errors)) {
$error['errors'] = $errors;
}

$log->error($e->getMessage());
if ('application/json' === $mediaType || true === $isAPI) {
$app->response->headers->set(
'Content-Type',
'application/json'
);
echo json_encode($error);
} else {
echo '<html>
<head><title>Error</title></head>
<body><h1>Error: ' . $error['code'] . '</h1><p>'
. $error['message']
.'</p></body></html>';
}

});

The $app->error() method receives the thrown exception as argument. By default I fetch all the data I need and fill the $error array, then if I’m in production mode I unset the private data and rewrite the message with a generic one. The custom ValidationException class has a custom getData() method that returns an array of validation errors that are added to the final payload. Then the error is displayed in JSON or HTML depending on the request. On the API side we can have a simple error like this:

$app->error()方法接收抛出的异常作为参数。 默认情况下,我获取所需的所有数据并填充$error数组,然后,如果我处于生产模式,则取消设置私有数据,并使用通用的重写消息。 定制的ValidationException类具有一个定制的getData()方法,该方法返回添加到最终有效负载中的验证错误数组。 然后根据请求以JSON或HTML显示错误。 在API方面,我们可能会遇到一个简单的错误,如下所示:

{
"code": 123,
"message": "An error occurred, a support person is being notified of this"
}

or a full validation error like this:

或像这样的完整验证错误:

{
"code": 0,
"message": "Invalid data",
"errors":
{
"contact":
[
{
"field": "email",
"message": "Email address already exists"
}
]
}
}

结论 (Conclusion)

We have the core of our API in place now. In the next part we’ll add some flesh in order to have a full functioning service. In the meanwhile, feel free to read through the articles linked throughout this part – they’re a treasure trove of useful API design principles.

我们现在已经拥有API的核心。 在下一部分中,我们将添加一些肉以提供完整的服务。 同时,随时阅读本部分中链接的文章-它们是有用的API设计原则的宝库。

翻译自: https://www.sitepoint.com/build-rest-api-scratch-introduction/

fcn从头开始

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值