1659211080
Movie,actor & director RESTful API. Sample app with jpa, flyway and testcontainers.
docker run -d -p 8080:8080 mangila/spring-restful-jpa-flyway
docker run -d -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=dev" mangila/spring-restful-jpa-flyway
docker-compose up -d
Download details:
Author: mangila
Source code: https://github.com/mangila/spring-restful-jpa-flyway
License: MIT license
#spring #java #springboot #restful
1659136320
This package is no longer maintained
This package is no longer maintained as Laravel already has a similar feature built-in since Laravel 5.8 and Laravel 6.x. Documentation on Laravel's API authentication can be found here. Furthermore, since Laravel 7.x, there is a new Laravel package called Airlock/Sanctum that would better serve API authentication purposes.
A simple way of authenticating your APIs with API keys using Laravel. This package uses the following libraries:
**Laravel 5.3.x onwards: ~4.*
**Laravel 5.1.x to 5.2.x: ~3.*
**Laravel 5.1.x: ~2.*
**Laravel 4.2.x: ~1.*
(Recently updated version for Laravel 4. Please note that there are namespace changes here)
**Laravel 4.2.x: 0.*
(The version that most of you are using)
Run composer require chrisbjr/api-guard 4.*
In your config/app.php
add Chrisbjr\ApiGuard\Providers\ApiGuardServiceProvider
to the end of the providers
array
'providers' => array(
...
Chrisbjr\ApiGuard\Providers\ApiGuardServiceProvider::class,
),
Now publish the migration and configuration files for api-guard:
$ php artisan vendor:publish --provider="Chrisbjr\ApiGuard\Providers\ApiGuardServiceProvider"
Then run the migration:
$ php artisan migrate
It will setup api_keys
table.
Once you're done with the required setup, you can now generate your first API key.
Run the following command to generate an API key:
php artisan api-key:generate
Generally, the ApiKey
object is a polymorphic object meaning this can belong to more than one other model.
To generate an API key that is linked to another object (a "user", for example), you can do the following:
+php artisan api-key:generate --id=1 --type="App\User"
To specify that a model can have API keys, you can attach the Apikeyable
trait to the model:
use Chrisbjr\ApiGuard\Models\Mixins\Apikeyable;
class User extends Model
{
use Apikeyable;
...
}
This will attach the following methods to the model:
// Get the API keys of the object
$user->apiKeys();
// Create an API key for the object
$user->createApiKey();
To generate an API key from within your application, you can use the following method in the ApiKey
model:
$apiKey = Chrisbjr\ApiGuard\Models\ApiKey::make()
// Attach a model to the API key
$apiKey = Chrisbjr\ApiGuard\Models\ApiKey::make($model)
You can start using ApiGuard by simply attaching the auth.apikey
middleware to your API route:
Route::middleware(['auth.apikey'])->get('/test', function (Request $request) {
return $request->user(); // Returns the associated model to the API key
});
This effectively secures your API with an API key which needs to specified in the X-Authorization
header. This can be configured in config/apiguard.php
.
Here is a sample cURL command to demonstrate:
curl -X GET \
http://apiguard.dev/api/test \
-H 'x-authorization: api-key-here'
You might also want to attach this middleware to your api
middleware group in your app/Http/Kernel.php
to take advantage of other Laravel features such as throttling.
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
...
'api' => [
'throttle:60,1',
'bindings',
'auth.apikey',
],
];
If you noticed in the basic example, you can also access the attached model to the API key by calling $request->user()
. We are attaching the related model in this method because in most use cases, this is actually the user.
Unauthorized requests will get a 401
status response with the following JSON:
{
"error": {
"code": "401",
"http_code": "GEN-UNAUTHORIZED",
"message": "Unauthorized."
}
}
The ApiGuardController
takes advantage of Fractal and api-response libraries.
This enables us to easily create APIs with models and use transformers to give a standardized JSON response.
Here is an example:
Let's say you have the following model:
use Illuminate\Database\Eloquent\Model;
class Book extends Model
{
protected $fillable = [
'name',
];
}
You can make a basic controller which will return all books like this:
use Chrisbjr\ApiGuard\Http\Controllers\ApiGuardController;
use App\Transformers\BookTransformer;
use App\Book;
class BooksController extends ApiGuardController
{
public function all()
{
$books = Book::all();
return $this->response->withCollection($books, new BookTransformer);
}
}
Now, you'll need to make the transformer for your Book object. Transformers help with defining and manipulating the variables you want to return to your JSON response.
use League\Fractal\TransformerAbstract;
use App\Book;
class BookTransformer extends TransformerAbstract
{
public function transform(Book $book)
{
return [
'id' => $book->id,
'name' => $book->name,
'created_at' => $book->created_at,
'updated_at' => $book->updated_at,
];
}
}
Once you have this accessible in your routes, you will get the following response from the controller:
{
"data": {
"id": 1,
"title": "The Great Adventures of Chris",
"created_at": {
"date": "2017-05-25 18:54:18",
"timezone_type": 3,
"timezone": "UTC"
},
"updated_at": {
"date": "2017-05-25 18:54:18",
"timezone_type": 3,
"timezone": "UTC"
}
}
}
More examples can be found on the Github page: https://github.com/ellipsesynergie/api-response.
To learn more about transformers, visit the PHP League's documentation on Fractal: Fractal
ApiGuard comes with a request class that can handle validation of requests for you and throw a standard response.
You can create a Request
class as you usually do but in order to get a standard JSON response you'll have to extend the ApiGuardFormRequest
class.
use Chrisbjr\ApiGuard\Http\Requests\ApiGuardFormRequest;
class BookStoreRequest extends ApiGuardFormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required',
];
}
}
Now you can use this in your controller as you normally do with Laravel:
use Chrisbjr\ApiGuard\Http\Controllers\ApiGuardController;
use App\Transformers\BookTransformer;
use App\Book;
class BooksController extends ApiGuardController
{
public function store(BookStoreRequest $request)
{
// Request should already be validated
$book = Book::create($request->all())
return $this->response->withItem($book, new BookTransformer);
}
}
If the request failed to pass the validation rules, it will return with a response like the following:
{
"error": {
"code": "GEN-UNPROCESSABLE",
"http_code": 422,
"message": {
"name": [
"The name field is required."
]
}
}
}
Author: Chrisbjr
Source Code: https://github.com/chrisbjr/api-guard
License: MIT license
1657407840
This package is not actively maintained because my focus is on other projects, but if there is bugs, I will try to fix it immediately.
kwdb
This is a monorepo for kwdb and its new cli tool(WIP). For their code checkout the corresponding folder in the
packages
folder.
Author: Shanoaice
Source Code: https://github.com/shanoaice/kwdb
License: MIT license
1657402800
Bake RESTful CakePHP controllers in seconds with this API focused bake template.
!!! note "" You can skip this step if MixerAPI is installed.
composer require mixerapi/bake
bin/cake plugin load MixerApi/Bake
Alternatively after composer installing you can manually load the plugin in your Application:
# src/Application.php
public function bootstrap(): void
{
// other logic...
$this->addPlugin('MixerApi/Bake');
}
MixerApi/Bake will automatically detect the following plugins and adjust the bake output accordingly:
Add --theme MixerApi/Bake
to your bake commands.
Bake all your controllers:
bin/cake bake controller all --theme MixerApi/Bake
Bake a single controller:
bin/cake bake controller {ControllerName} --theme MixerApi/Bake
Bake everything (theme only impacts controllers):
bin/cake bake all --everything --theme MixerApi/Bake
Read more at MixerAPI.com.
Author: Mixerapi
Source Code: https://github.com/mixerapi/bake
License: View license
1656039384
Create awesome APIs using express.
Register mongoose resources and default RESTful routes are automatically made
var express = require('express'),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
morgan = require('morgan'),
restful = require('node-restful'),
mongoose = restful.mongoose;
var app = express();
app.use(morgan('dev'));
app.use(bodyParser.urlencoded({'extended':'true'}));
app.use(bodyParser.json());
app.use(bodyParser.json({type:'application/vnd.api+json'}));
app.use(methodOverride());
mongoose.connect("mongodb://localhost/resources");
var Resource = app.resource = restful.model('resource', mongoose.Schema({
title: String,
year: Number,
}))
.methods(['get', 'post', 'put', 'delete']);
Resource.register(app, '/resources');
app.listen(3000);
Registers the following routes:
GET /resources
GET /resources/:id
POST /resources
PUT /resources/:id
DELETE /resources/:id
which do exactly what you think they do!
The best part is that restful.model
returns a Mongoose model, so you can interact with it the same way that you're already accustomed to! (i.e. new Resource
, Resource.findById
, etc.)
This library is currently supported through complaint driven development, so if you see something, have a feature request, open an issue and if it seems to jive with the mission of the library, I'll prioritize it.
npm install node-restful
There is a good example application under examples/movies.
I will also show some features and use cases for them, how to set up routes, etc.
There are a few functions that are available after we register the mongoose schema. The first one we already saw.
.methods([...])
takes a list of methods that should be available on the resource. Future calls to methods will override previously set values To disallow delete
operations, simply run
Resource.methods(['get', 'post', 'put'])
We can also run custom routes. We can add custom routes by calling .route(path, handler)
Resource.route('recommend', function(req, res, next) {
res.send('I have a recommendation for you!');
});
This will set up a route at /resources/recommend
, which will be called on all HTTP methods. We can also restrict the HTTP method by adding it to the path:
Resource.route('recommend.get', function(req, res, next) {
res.send('GET a recommendation');
});
Resource.route('recommend', ['get', 'put', 'delete'], function(req, res, next) { ... });
Or do some combination of HTTP methods.
Now. Lets say we have to run arbitrary code before or after a route. Lets say we need to hash a password before a POST or PUT operation. Well, easy.
Resource.before('post', hash_password)
.before('put', hash_password);
function hash_password(req, res, next) {
req.body.password = hash(req.body.password);
next();
}
Boy. That was easy. What about doing stuff after request, but before its sent back to the user? Restful stores the bundle of data to be returned in res.locals
(see express docs). res.locals.status_code
is the returned status code and res.locals.bundle
is the bundle of data. In every before and after call, you are free to modify these are you see fit!
Resource.after('get', function(req, res, next) {
var tmp = res.locals.bundle.title; // Lets swap the title and year fields because we're funny!
res.locals.bundle.title = res.locals.bundle.year;
res.locals.bundle.year = tmp;
next(); // Don't forget to call next!
});
Resource.after('recommend', do_something); // Runs after all HTTP verbs
Now, this is all great. But up until now we've really only talked about defining list routes, those at /resources/route_name
. We can also define detail routes. Those look like this
Resource.route('moreinfo', {
detail: true,
handler: function(req, res, next) {
// req.params.id holds the resource's id
res.send("I'm at /resources/:id/moreinfo!")
}
});
I don't think this is the prettiest, and I'd be open to suggestions on how to beautify detail route definition...
And that's everything for now!
Node-restful accepts many options to manipulate the list results. These options can be added to your request either via the querystring or the POST body. They are passed into the mongoose query to filter your resultset.
If you only need a few properties instead of the entire model, you can ask the service to only give just the properties you need:
A GET
request to /users/?select=name%20email
will result in:
[
{
"_id": "543adb9c7a0f149e3ac29438",
"name": "user1",
"email": "user1@test.com"
},
{
"_id": "543adb9c7a0f149e3ac2943b",
"name": "user2",
"email": "user2@test.com"
}
]
When implementing pagination you might want to use skip
and limit
filters. Both do exactly what their name says and just skip given amount of items or limit to a set amount of items.
/users/?limit=5
will give you the first 5 items/users/?skip=5
will skip the first 5 and give you the rest/users/?limit=5&skip=5
will skip the first 5 and then give you the second 5
Getting a sorted list is as easy as adding a sort
querystring parameter with the property you want to sort on. /users/?sort=name
will give you a list sorted on the name property, with an ascending sort order.
Changing the sort order uses the same rules as the string notation of mongoose's sort filter. /users/?sort=-name
will return the same list as before with a descending sort order.
Sometimes you just want to get all people older than 18, or you are want to get all people living in a certain city. Then you would want to use filters for that. You can ask the service for equality, or values greater or less than, give it an array of values it should match to, or even a regex.
Filter | Query | Example | Description |
---|---|---|---|
equal | equals | /users?gender=male or /users?gender__equals=male | both return all male users |
not equal | ne | /users?gender__ne=male | returns all users who are not male (female and x ) |
greater than | gt | /users?age__gt=18 | returns all users older than 18 |
greater than or equal to | gte | /users?age__gte=18 | returns all users 18 and older (age should be a number property) |
less than | lt | /users?age__lt=30 | returns all users age 29 and younger |
less than or equal to | lte | /users?age__lte=30 | returns all users age 30 and younger |
in | in | /users?gender__in=female,male | returns all female and male users |
nin | nin | /users?age__nin=18,30 | returns all users with age other than 18 or 30 |
Regex | regex | /users?username__regex=/^baugarten/i | returns all users with a username starting with baugarten |
When you have setup a mongoose Schema with properties referencing other entities, you can ask the service to populate them for you.
A GET
request to /users/542edff9fffc55dd29d99346
will result in:
{
"_id": "542edff9fffc55dd29d99346",
"name": "the employee",
"email": "employee@company.com",
"boss": "542edff9fffc55dd29d99343",
"__v": 0
}
A GET
request to /users/542edff9fffc55dd29d99346?populate=boss
will result in:
{
"_id": "542edff9fffc55dd29d99346",
"name": "the employee",
"email": "employee@company.com",
"boss": {
"_id": "542edff9fffc55dd29d99343",
"name": "the boss",
"email": "boss@company.com",
"__v": 0
},
"__v": 0
}
You can view the issue list for what I'm working on, or contact me to help!
Just reach out to me
Copyright (c) 2012 by Ben Augarten
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Author: baugarten
Download Link: Download The Source Code
Official Website: https://github.com/baugarten/node-restful
License: MIT License
1655566871
In this tutorial, we'll build a RESTful API that implements the CRUD (Create, Read, Update, Delete) operations for an employee. For data persistence, we'll be using a MongoDB Atlas cluster.
Part 2: ☞ https://morioh.com/p/b970a38ef3db?f=60d94da986deb67e97e9ce2b
Timeline:
1:13 Finished Product
2:30 MEAN Stack Architecture
3:55 Pre-requisites for tutorial series
4:30 Building the server-side Node and Express app
6:50 Installing dependencies
13:45 Creating an Employee Interface
15:00 Connecting to the database
25:00 Build the RESTFUL API
26:20 GET /employees
28:20 GET /employees/:id
31:30 PUT /employees/:id
32:45 DELETE /employees/:id
33:45 Register the routes
#restful #mongodb #express #node
1655347260
REST API は、サーバーと通信するために複数のクライアント(またはAPPS)が使用できるアプリケーションプログラミングインターフェイスです。
Rest APIは、アプリケーションに必要なデータを便利な形式(JSONやXMLなど)で保存および取得する一種のWebサービスです。
ステートレスであるため、Webサービスにアクセスするために依存するコードライブラリを必要としないため、開発者に大きな柔軟性を提供します。
RESTでサポートされている多くのプロトコルの中で、最も一般的なものはHTTPです。
HTTPRequestを使用してクライアントから要求が送信されると、対応する応答がHTTPResponseを使用してサーバーから送信されます。リクエストとレスポンスでサポートされている最も広く使用されている機械可読形式は、JSON(Javascript Object Notification)とXML(Extensible Markup Language)です。
RESTは、コンピューター科学者のROYFIELDINGによって作成されました。
REST APIを使用して、さまざまなアクションを実行できます。アクションに基づいて、関連する方法を使用する必要があります。以下は、RESTでサポートされている5つの方法です。
これを例で見てみましょう。私たちは、母親が十分な休息をとることが決してないことを知っています。しかし、これらの休むことのないママを例として取り上げ、RestAPIをどのように使用するかを見てみましょう。:)
生まれたばかりの赤ちゃんによる過度の要求の中で、おむつはリーダーボードで最初に位置します。
母親は赤ちゃんのためにすべてが最善であることを望んでいます。ですから、母親が赤ちゃんに最適なおむつを選びたいと思うのは明らかです。そこで、彼女はショッピングWebサイト(フリップカートを想定)にアクセスして、おむつを検索します。これにより、すべてのおむつのリストを取得するために、フリップカートのサーバーにHTTPリクエストが送信されます。FlipkartのサーバーはHTTP応答で応答します。これは、いくつかの基本的な詳細を含むおむつのリストを含むJSONオブジェクト(想定)になります。FlipkartのWebサイトは、この応答を読み取り、人間が読める形式に変換して、母親が見ることができるようにWebページに表示します。
彼女が生まれたばかりの赤ちゃんのために特定のおむつを選び、それを彼女のリストに追加した後。これにより、POSTリクエストが作成され、おむつのブランド、サイズ、数量、価格などを含む新しいレコードがフリップカートのデータベースに作成されます。
彼女の赤ちゃんは成長を続け、すぐに新生児のサイズを超えます。母親がまだおむつのブランドを気に入っていて、サイズを大きくしたいとします。彼女がしなければならないのは、新しいおむつのサイズを選択することだけです。彼女がおむつのサイズを新生児のサイズからサイズ1に更新すると、これによりPATCHメソッドがトリガーされ、他のすべては同じままで、おむつのサイズのみが変更されます。
母親が現在のブランドを変更し、別のブランドに切り替えることを決定することは非常に一般的です。ここで、母親はPUTリクエストを開始します。ここで、以前に選択されたブランドを含むデータ全体が変更され、新しく選択されたブランドに対応するデータに置き換えられます。
最後に、いくつかのGET、POST、PUT、およびPATCHを含む一連の実験の後、母親が子供をトイレトレーニングする時が来ました。彼女が子供を訓練することに成功した場合、おむつはもはや必要ありません。これにより、 DELETE要求がトリガーされます。
前提条件
ステップ1
Visual Studio 2022を開き、asp.netコアwebapiプロジェクトを作成します。
ステップ2
NugetからNpgsql.EntityFrameworkCore.PostgreSQLとMicrosoft.EntityFrameworkCore.Toolsをインストールします
ステップ3
Product.csとOrder.csを作成します
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("product")]
public class Product {
[Key, Required]
public int id {
get;
set;
}
[Required]
public string ? name {
get;
set;
}
public string ? brand {
get;
set;
}
public string ? size {
get;
set;
}
public decimal price {
get;
set;
}
public virtual ICollection < Order > orders {
get;
set;
}
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("order")]
public class Order {
[Key, Required]
public int id {
get;
set;
}
public int product_id {
get;
set;
}
[Required]
public string ? name {
get;
set;
}
public string ? address {
get;
set;
}
public string ? phone {
get;
set;
}
public DateTime createdon {
get;
set;
}
public virtual Product product {
get;
set;
}
}
ステップ4
DbContextクラスから継承されたEF_DataContextを作成します
using Microsoft.EntityFrameworkCore;
public class EF_DataContext: DbContext {
public EF_DataContext(DbContextOptions < EF_DataContext > options): base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.UseSerialColumns();
}
public DbSet <Product> Products {
get;
set;
}
public DbSet <Order> Orders {
get;
set;
}
}
ステップ5
appsetting.jsonを開きます
"ConnectionStrings": {
"Ef_Postgres_Db": "Server=localhost;Database=shopingpostgres;Port=5432;User Id=postgres;Password=qwerty1234;"
}
ステップ6
Program.csを開く
builder.Services.AddDbContext < EF_DataContext > (o => o.UseNpgsql(builder.Configuration.GetConnectionString("Ef_Postgres_Db")));
ステップ7
2つのコマンドを実行します
Add-Migration InitialDatabase
Update-Database
ステップ8
API通信に使用されるAPI製品モデルと注文モデルを作成します
public class Product {
public int id {
get;
set;
}
public string ? name {
get;
set;
}
public string ? brand {
get;
set;
}
public string ? size {
get;
set;
}
public decimal price {
get;
set;
}
}
public class Order {
public int id {
get;
set;
}
public int product_id {
get;
set;
}
public string ? name {
get;
set;
}
public string ? address {
get;
set;
}
public string ? phone {
get;
set;
}
public DateTime createdon {
get;
set;
}
public virtual Product product {
get;
set;
}
}
ステップ9
データベースと通信するDBhelperクラスを追加します
using ShoppingWebApi.EfCore;
namespace ShoppingWebApi.Model {
public class DbHelper {
private EF_DataContext _context;
public DbHelper(EF_DataContext context) {
_context = context;
}
/// <summary>
/// GET
/// </summary>
/// <returns></returns>
public List < ProductModel > GetProducts() {
List < ProductModel > response = new List < ProductModel > ();
var dataList = _context.Products.ToList();
dataList.ForEach(row => response.Add(new ProductModel() {
brand = row.brand,
id = row.id,
name = row.name,
price = row.price,
size = row.size
}));
return response;
}
public ProductModel GetProductById(int id) {
ProductModel response = new ProductModel();
var row = _context.Products.Where(d => d.id.Equals(id)).FirstOrDefault();
return new ProductModel() {
brand = row.brand,
id = row.id,
name = row.name,
price = row.price,
size = row.size
};
}
/// <summary>
/// It serves the POST/PUT/PATCH
/// </summary>
public void SaveOrder(OrderModel orderModel) {
Order dbTable = new Order();
if (orderModel.id > 0) {
//PUT
dbTable = _context.Orders.Where(d => d.id.Equals(orderModel.id)).FirstOrDefault();
if (dbTable != null) {
dbTable.phone = orderModel.phone;
dbTable.address = orderModel.address;
}
} else {
//POST
dbTable.phone = orderModel.phone;
dbTable.address = orderModel.address;
dbTable.name = orderModel.name;
dbTable.Product = _context.Products.Where(f => f.id.Equals(orderModel.product_id)).FirstOrDefault();
_context.Orders.Add(dbTable);
}
_context.SaveChanges();
}
/// <summary>
/// DELETE
/// </summary>
/// <param name="id"></param>
public void DeleteOrder(int id) {
var order = _context.Orders.Where(d => d.id.Equals(id)).FirstOrDefault();
if (order != null) {
_context.Orders.Remove(order);
_context.SaveChanges();
}
}
}
}
ステップ10
Apiコントローラーを作成し、ShoppingRestApiという名前を付けます
using Microsoft.AspNetCore.Mvc;
using ShoppingWebApi.EfCore;
using ShoppingWebApi.Model;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace ShoppingWebApi.Controllers {
[ApiController]
public class ShoppingApiController: ControllerBase {
private readonly DbHelper _db;
public ShoppingApiController(EF_DataContext eF_DataContext) {
_db = new DbHelper(eF_DataContext);
}
// GET: api/<ShoppingApiController>
[HttpGet]
[Route("api/[controller]/GetProducts")]
public IActionResult Get() {
ResponseType type = ResponseType.Success;
try {
IEnumerable < ProductModel > data = _db.GetProducts();
if (!data.Any()) {
type = ResponseType.NotFound;
}
return Ok(ResponseHandler.GetAppResponse(type, data));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// GET api/<ShoppingApiController>/5
[HttpGet]
[Route("api/[controller]/GetProductById/{id}")]
public IActionResult Get(int id) {
ResponseType type = ResponseType.Success;
try {
ProductModel data = _db.GetProductById(id);
if (data == null) {
type = ResponseType.NotFound;
}
return Ok(ResponseHandler.GetAppResponse(type, data));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// POST api/<ShoppingApiController>
[HttpPost]
[Route("api/[controller]/SaveOrder")]
public IActionResult Post([FromBody] OrderModel model) {
try {
ResponseType type = ResponseType.Success;
_db.SaveOrder(model);
return Ok(ResponseHandler.GetAppResponse(type, model));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// PUT api/<ShoppingApiController>/5
[HttpPut]
[Route("api/[controller]/UpdateOrder")]
public IActionResult Put([FromBody] OrderModel model) {
try {
ResponseType type = ResponseType.Success;
_db.SaveOrder(model);
return Ok(ResponseHandler.GetAppResponse(type, model));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// DELETE api/<ShoppingApiController>/5
[HttpDelete]
[Route("api/[controller]/DeleteOrder/{id}")]
public IActionResult Delete(int id) {
try {
ResponseType type = ResponseType.Success;
_db.DeleteOrder(id);
return Ok(ResponseHandler.GetAppResponse(type, "Delete Successfully"));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
}
}
ステップ11
API応答を処理する応答モデルと応答ハンドラーを追加します
namespace ShoppingWebApi.Model {
public class ApiResponse {
public string Code {
get;
set;
}
public string Message {
get;
set;
}
public object ? ResponseData {
get;
set;
}
}
public enum ResponseType {
Success,
NotFound,
Failure
}
}
次に、ビデオで示されているように、POSTMANを使用してAPIをテストします。
このストーリーは、もともとhttps://www.c-sharpcorner.com/article/restful-api-in-net-core-using-ef-core-and-postgres/で公開されました
1655347140
REST API es una interfaz de programación de aplicaciones que pueden utilizar varios clientes (o APLICACIONES) para comunicarse con un servidor.
Rest API es un tipo de servicio web que almacena y recupera los datos necesarios para su aplicación en un formato conveniente (por ejemplo, JSON o XML).
Proporciona una gran flexibilidad a los desarrolladores, ya que no necesita ninguna biblioteca de código dependiente para acceder a los servicios web, ya que no tiene estado.
Entre los muchos protocolos admitidos por REST, el más común es HTTP .
Cuando se envía una solicitud desde el cliente mediante HTTPRequest , se envía una respuesta correspondiente desde el servidor mediante HTTPResponse . Los formatos legibles por máquina más utilizados y admitidos para solicitudes y respuestas son JSON (Notificación de objetos Javascript) y XML (Lenguaje de marcado extensible).
REST fue creado por el informático ROY FIELDING .
Las API REST se pueden usar para realizar diferentes acciones. En función de las acciones, se debe utilizar el método pertinente. Los siguientes son los 5 métodos compatibles con REST.
Veamos esto con un ejemplo. Sabemos que las madres nunca descansan lo suficiente. Pero tomemos estas mamás sin descanso como ejemplo y veamos cómo usan Rest API. :)
Entre las demandas excesivas de un bebé recién nacido, el cambio de pañales ocupa el primer lugar en la tabla de clasificación.
Una madre quiere todo lo mejor para el bebé. Entonces, es obvio que la madre querría elegir el mejor pañal para su bebé. Entonces, ella va a un sitio web de compras (supongamos: flipkart) y busca pañales. Esto enviará una solicitud HTTP al servidor de flipkart para OBTENER la lista de todos los pañales. El servidor de Flipkart responde con una respuesta HTTP que será un objeto JSON (supongamos) que contiene una lista de pañales con algunos detalles básicos. El sitio web de Flipkart lee esta Respuesta, la convierte a un formato legible por humanos y la muestra en la página web para que la madre la vea.
Después, elige un pañal en particular para su bebé recién nacido y lo agrega a su lista. Esto crea una solicitud POST donde se crea un nuevo registro en la base de datos de flipkart que contiene la marca, el tamaño, la cantidad, el precio, etc. del pañal.
Su bebé sigue creciendo y pronto supera el tamaño del recién nacido. Supongamos que a la madre todavía le gusta la marca de pañales y solo quiere aumentar su tamaño, todo lo que tiene que hacer es elegir el nuevo tamaño de pañal. Cuando actualiza el tamaño del pañal del tamaño recién nacido al tamaño 1, se activa un método PATCH donde todo lo demás permanece igual y solo se cambia el tamaño del pañal.
Es muy común que la madre cambie la marca actual y decida cambiar a una alternativa. Aquí, la madre iniciará una solicitud PUT donde todos los datos que contienen la marca elegida previamente se modifican y reemplazan con los datos correspondientes a la marca recién elegida.
Finalmente, después de una serie de experimentos que involucran varios GET, POST, PUT y PATCH, es hora de que la madre enseñe al niño a ir al baño. Si logra entrenar al niño, ya no necesitará los pañales. Esto activa una solicitud DELETE .
PRERREQUISITOS
Paso 1
Abra Visual Studio 2022 y cree el proyecto asp.net core webapi.
Paso 2
Instale Npgsql.EntityFrameworkCore.PostgreSQL y Microsoft.EntityFrameworkCore.Tools desde Nuget
Paso 3
Crear Product.cs y Order.cs
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("product")]
public class Product {
[Key, Required]
public int id {
get;
set;
}
[Required]
public string ? name {
get;
set;
}
public string ? brand {
get;
set;
}
public string ? size {
get;
set;
}
public decimal price {
get;
set;
}
public virtual ICollection < Order > orders {
get;
set;
}
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("order")]
public class Order {
[Key, Required]
public int id {
get;
set;
}
public int product_id {
get;
set;
}
[Required]
public string ? name {
get;
set;
}
public string ? address {
get;
set;
}
public string ? phone {
get;
set;
}
public DateTime createdon {
get;
set;
}
public virtual Product product {
get;
set;
}
}
Paso 4
Crear EF_DataContext heredado de la clase DbContext
using Microsoft.EntityFrameworkCore;
public class EF_DataContext: DbContext {
public EF_DataContext(DbContextOptions < EF_DataContext > options): base(options) {}
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.UseSerialColumns();
}
public DbSet <Product> Products {
get;
set;
}
public DbSet <Order> Orders {
get;
set;
}
}
Paso 5
Abrir appsetting.json
"ConnectionStrings": {
"Ef_Postgres_Db": "Server=localhost;Database=shopingpostgres;Port=5432;User Id=postgres;Password=qwerty1234;"
}
Paso 6
Abrir programa.cs
builder.Services.AddDbContext < EF_DataContext > (o => o.UseNpgsql(builder.Configuration.GetConnectionString("Ef_Postgres_Db")));
Paso 7
Ejecuta los 2 comandos
Add-Migration InitialDatabase
Update-Database
Paso 8
Cree los modelos de pedido y producto API que se utilizarán para la comunicación API
public class Product {
public int id {
get;
set;
}
public string ? name {
get;
set;
}
public string ? brand {
get;
set;
}
public string ? size {
get;
set;
}
public decimal price {
get;
set;
}
}
public class Order {
public int id {
get;
set;
}
public int product_id {
get;
set;
}
public string ? name {
get;
set;
}
public string ? address {
get;
set;
}
public string ? phone {
get;
set;
}
public DateTime createdon {
get;
set;
}
public virtual Product product {
get;
set;
}
}
Paso 9
Agregue la clase DBhelper que hablará con su base de datos
using ShoppingWebApi.EfCore;
namespace ShoppingWebApi.Model {
public class DbHelper {
private EF_DataContext _context;
public DbHelper(EF_DataContext context) {
_context = context;
}
/// <summary>
/// GET
/// </summary>
/// <returns></returns>
public List < ProductModel > GetProducts() {
List < ProductModel > response = new List < ProductModel > ();
var dataList = _context.Products.ToList();
dataList.ForEach(row => response.Add(new ProductModel() {
brand = row.brand,
id = row.id,
name = row.name,
price = row.price,
size = row.size
}));
return response;
}
public ProductModel GetProductById(int id) {
ProductModel response = new ProductModel();
var row = _context.Products.Where(d => d.id.Equals(id)).FirstOrDefault();
return new ProductModel() {
brand = row.brand,
id = row.id,
name = row.name,
price = row.price,
size = row.size
};
}
/// <summary>
/// It serves the POST/PUT/PATCH
/// </summary>
public void SaveOrder(OrderModel orderModel) {
Order dbTable = new Order();
if (orderModel.id > 0) {
//PUT
dbTable = _context.Orders.Where(d => d.id.Equals(orderModel.id)).FirstOrDefault();
if (dbTable != null) {
dbTable.phone = orderModel.phone;
dbTable.address = orderModel.address;
}
} else {
//POST
dbTable.phone = orderModel.phone;
dbTable.address = orderModel.address;
dbTable.name = orderModel.name;
dbTable.Product = _context.Products.Where(f => f.id.Equals(orderModel.product_id)).FirstOrDefault();
_context.Orders.Add(dbTable);
}
_context.SaveChanges();
}
/// <summary>
/// DELETE
/// </summary>
/// <param name="id"></param>
public void DeleteOrder(int id) {
var order = _context.Orders.Where(d => d.id.Equals(id)).FirstOrDefault();
if (order != null) {
_context.Orders.Remove(order);
_context.SaveChanges();
}
}
}
}
Paso 10
Cree su controlador Api, asígnele el nombre ShoppingRestApi
using Microsoft.AspNetCore.Mvc;
using ShoppingWebApi.EfCore;
using ShoppingWebApi.Model;
// For more information on enabling Web API for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860
namespace ShoppingWebApi.Controllers {
[ApiController]
public class ShoppingApiController: ControllerBase {
private readonly DbHelper _db;
public ShoppingApiController(EF_DataContext eF_DataContext) {
_db = new DbHelper(eF_DataContext);
}
// GET: api/<ShoppingApiController>
[HttpGet]
[Route("api/[controller]/GetProducts")]
public IActionResult Get() {
ResponseType type = ResponseType.Success;
try {
IEnumerable < ProductModel > data = _db.GetProducts();
if (!data.Any()) {
type = ResponseType.NotFound;
}
return Ok(ResponseHandler.GetAppResponse(type, data));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// GET api/<ShoppingApiController>/5
[HttpGet]
[Route("api/[controller]/GetProductById/{id}")]
public IActionResult Get(int id) {
ResponseType type = ResponseType.Success;
try {
ProductModel data = _db.GetProductById(id);
if (data == null) {
type = ResponseType.NotFound;
}
return Ok(ResponseHandler.GetAppResponse(type, data));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// POST api/<ShoppingApiController>
[HttpPost]
[Route("api/[controller]/SaveOrder")]
public IActionResult Post([FromBody] OrderModel model) {
try {
ResponseType type = ResponseType.Success;
_db.SaveOrder(model);
return Ok(ResponseHandler.GetAppResponse(type, model));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// PUT api/<ShoppingApiController>/5
[HttpPut]
[Route("api/[controller]/UpdateOrder")]
public IActionResult Put([FromBody] OrderModel model) {
try {
ResponseType type = ResponseType.Success;
_db.SaveOrder(model);
return Ok(ResponseHandler.GetAppResponse(type, model));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
// DELETE api/<ShoppingApiController>/5
[HttpDelete]
[Route("api/[controller]/DeleteOrder/{id}")]
public IActionResult Delete(int id) {
try {
ResponseType type = ResponseType.Success;
_db.DeleteOrder(id);
return Ok(ResponseHandler.GetAppResponse(type, "Delete Successfully"));
} catch (Exception ex) {
return BadRequest(ResponseHandler.GetExceptionResponse(ex));
}
}
}
}
Paso 11
Agregue el modelo de respuesta y el controlador de respuesta que manejará sus respuestas API
namespace ShoppingWebApi.Model {
public class ApiResponse {
public string Code {
get;
set;
}
public string Message {
get;
set;
}
public object ? ResponseData {
get;
set;
}
}
public enum ResponseType {
Success,
NotFound,
Failure
}
}
Ahora pruebe las API usando POSTMAN como se muestra en el video.
Esta historia se publicó originalmente en https://www.c-sharpcorner.com/article/restful-api-in-net-core-using-ef-core-and-postgres/
1654364820
ozzo-routing
You may consider using go-rest-api to jumpstart your new RESTful applications with ozzo-routing.
ozzo-routing is a Go package that provides high performance and powerful HTTP routing capabilities for Web applications. It has the following features:
http.Handler
and http.HandlerFunc
If you are using fasthttp, you may use a similar routing package fasthttp-routing which is adapted from ozzo-routing.
Go 1.13 or above.
In your Go project using go mod
, run the following command to install the package:
go get github.com/go-ozzo/ozzo-routing/v2
For a complete RESTful application boilerplate based on ozzo-routing, please refer to the golang-restful-starter-kit. Below we describe how to create a simple REST API using ozzo-routing.
Create a server.go
file with the following content:
package main
import (
"log"
"net/http"
"github.com/go-ozzo/ozzo-routing/v2"
"github.com/go-ozzo/ozzo-routing/v2/access"
"github.com/go-ozzo/ozzo-routing/v2/slash"
"github.com/go-ozzo/ozzo-routing/v2/content"
"github.com/go-ozzo/ozzo-routing/v2/fault"
"github.com/go-ozzo/ozzo-routing/v2/file"
)
func main() {
router := routing.New()
router.Use(
// all these handlers are shared by every route
access.Logger(log.Printf),
slash.Remover(http.StatusMovedPermanently),
fault.Recovery(log.Printf),
)
// serve RESTful APIs
api := router.Group("/api")
api.Use(
// these handlers are shared by the routes in the api group only
content.TypeNegotiator(content.JSON, content.XML),
)
api.Get("/users", func(c *routing.Context) error {
return c.Write("user list")
})
api.Post("/users", func(c *routing.Context) error {
return c.Write("create a new user")
})
api.Put(`/users/<id:\d+>`, func(c *routing.Context) error {
return c.Write("update user " + c.Param("id"))
})
// serve index file
router.Get("/", file.Content("ui/index.html"))
// serve files under the "ui" subdirectory
router.Get("/*", file.Server(file.PathMap{
"/": "/ui/",
}))
http.Handle("/", router)
http.ListenAndServe(":8080", nil)
}
Create an HTML file ui/index.html
with any content.
Now run the following command to start the Web server:
go run server.go
You should be able to access URLs such as http://localhost:8080
, http://localhost:8080/api/users
.
ozzo-routing works by building a routing table in a router and then dispatching HTTP requests to the matching handlers found in the routing table. An intuitive illustration of a routing table is as follows:
Routes | Handlers |
---|---|
GET /users | m1, m2, h1, ... |
POST /users | m1, m2, h2, ... |
PUT /users/<id> | m1, m2, h3, ... |
DELETE /users/<id> | m1, m2, h4, ... |
For an incoming request GET /users
, the first route would match and the handlers m1, m2, and h1 would be executed. If the request is PUT /users/123
, the third route would match and the corresponding handlers would be executed. Note that the token <id>
can match any number of non-slash characters and the matching part can be accessed as a path parameter value in the handlers.
If an incoming request matches multiple routes in the table, the route added first to the table will take precedence. All other matching routes will be ignored.
The actual implementation of the routing table uses a variant of the radix tree data structure, which makes the routing process as fast as working with a hash table, thanks to the inspiration from httprouter.
To add a new route and its handlers to the routing table, call the To
method like the following:
router := routing.New()
router.To("GET", "/users", m1, m2, h1)
router.To("POST", "/users", m1, m2, h2)
You can also use shortcut methods, such as Get
, Post
, Put
, etc., which are named after the HTTP method names:
router.Get("/users", m1, m2, h1)
router.Post("/users", m1, m2, h2)
If you have multiple routes with the same URL path but different HTTP methods, like the above example, you can chain them together as follows,
router.Get("/users", m1, m2, h1).Post(m1, m2, h2)
If you want to use the same set of handlers to handle the same URL path but different HTTP methods, you can take the following shortcut:
router.To("GET,POST", "/users", m1, m2, h)
A route may contain parameter tokens which are in the format of <name:pattern>
, where name
stands for the parameter name, and pattern
is a regular expression which the parameter value should match. A token <name>
is equivalent to <name:[^/]*>
, i.e., it matches any number of non-slash characters. At the end of a route, an asterisk character can be used to match any number of arbitrary characters. Below are some examples:
/users/<username>
: matches /users/admin
/users/accnt-<id:\d+>
: matches /users/accnt-123
, but not /users/accnt-admin
/users/<username>/*
: matches /users/admin/profile/address
When a URL path matches a route, the matching parameters on the URL path can be accessed via Context.Param()
:
router := routing.New()
router.Get("/users/<username>", func (c *routing.Context) error {
fmt.Fprintf(c.Response, "Name: %v", c.Param("username"))
return nil
})
Route group is a way of grouping together the routes which have the same route prefix. The routes in a group also share the same handlers that are registered with the group via its Use
method. For example,
router := routing.New()
api := router.Group("/api")
api.Use(m1, m2)
api.Get("/users", h1).Post(h2)
api.Put("/users/<id>", h3).Delete(h4)
The above /api
route group establishes the following routing table:
Routes | Handlers |
---|---|
GET /api/users | m1, m2, h1, ... |
POST /api/users | m1, m2, h2, ... |
PUT /api/users/<id> | m1, m2, h3, ... |
DELETE /api/users/<id> | m1, m2, h4, ... |
As you can see, all these routes have the same route prefix /api
and the handlers m1
and m2
. In other similar routing frameworks, the handlers registered with a route group are also called middlewares.
Route groups can be nested. That is, a route group can create a child group by calling the Group()
method. The router serves as the top level route group. A child group inherits the handlers registered with its parent group. For example,
router := routing.New()
router.Use(m1)
api := router.Group("/api")
api.Use(m2)
users := api.Group("/users")
users.Use(m3)
users.Put("/<id>", h1)
Because the router serves as the parent of the api
group which is the parent of the users
group, the PUT /api/users/<id>
route is associated with the handlers m1
, m2
, m3
, and h1
.
Router manages the routing table and dispatches incoming requests to appropriate handlers. A router instance is created by calling the routing.New()
method.
Because Router
implements the http.Handler
interface, it can be readily used to serve subtrees on existing Go servers. For example,
router := routing.New()
http.Handle("/", router)
http.ListenAndServe(":8080", nil)
A handler is a function with the signature func(*routing.Context) error
. A handler is executed by the router if the incoming request URL path matches the route that the handler is associated with. Through the routing.Context
parameter, you can access the request information in handlers.
A route may be associated with multiple handlers. These handlers will be executed in the order that they are registered to the route. The execution sequence can be terminated in the middle using one of the following two methods:
Context.Abort()
: the router will simply skip the rest of the handlers. There is no error to be handled.A handler can call Context.Next()
to explicitly execute the rest of the unexecuted handlers and take actions after they finish execution. For example, a response compression handler may start the output buffer, call Context.Next()
, and then compress and send the output to response.
For each incoming request, a routing.Context
object is populated with the request information and passed through the handlers that need to handle the request. Handlers can get the request information via Context.Request
and send a response back via Context.Response
. The Context.Param()
method allows handlers to access the URL path parameters that match the current route.
Using Context.Get()
and Context.Set()
, handlers can share data between each other. For example, an authentication handler can store the authenticated user identity by calling Context.Set()
, and other handlers can retrieve back the identity information by calling Context.Get()
.
Context provides a few shortcut methods to read query parameters. The Context.Query()
method returns the named URL query parameter value; the Context.PostForm()
method returns the named parameter value in the POST or PUT body parameters; and the Context.Form()
method returns the value from either POST/PUT or URL query parameters.
The Context.Read()
method supports reading data from the request body and populating it into an object. The method will check the Content-Type
HTTP header and parse the body data as the corresponding format. For example, if Content-Type
is application/json
, the request body will be parsed as JSON data. The public fields in the object being populated will receive the parsed data if the data contains the same named fields. For example,
func foo(c *routing.Context) error {
data := &struct{
A string
B bool
}{}
// assume the body data is: {"A":"abc", "B":true}
// data will be populated as: {A: "abc", B: true}
if err := c.Read(&data); err != nil {
return err
}
}
By default, Context
supports reading data that are in JSON, XML, form, and multipart-form data. You may modify routing.DataReaders
to add support for other data formats.
Note that when the data is read as form data, you may use struct tag named form
to customize the name of the corresponding field in the form data. The form data reader also supports populating data into embedded objects which are either named or anonymous.
The Context.Write()
method can be used to write data of arbitrary type to the response. By default, if the data being written is neither a string nor a byte array, the method will will call fmt.Fprint()
to write the data into the response.
You can call Context.SetWriter()
to replace the default data writer with a customized one. For example, the content.TypeNegotiator
will negotiate the content response type and set the data writer with an appropriate one.
A handler may return an error indicating some erroneous condition. Sometimes, a handler or the code it calls may cause a panic. Both should be handled properly to ensure best user experience. It is recommended that you use the fault.Recover
handler or a similar error handler to handle these errors.
If an error is not handled by any handler, the router will handle it by calling its handleError()
method which simply sets an appropriate HTTP status code and writes the error message to the response.
When an incoming request has no matching route, the router will call the handlers registered via the Router.NotFound()
method. All the handlers registered via Router.Use()
will also be called in advance. By default, the following two handlers are registered with Router.NotFound()
:
routing.MethodNotAllowedHandler
: a handler that sends an Allow
HTTP header indicating the allowed HTTP methods for a requested URLrouting.NotFoundHandler
: a handler triggering 404 HTTP errorStatic files can be served with the help of file.Server
and file.Content
handlers. The former serves files under the specified directories, while the latter serves the content of a single file. For example,
import (
"github.com/go-ozzo/ozzo-routing/v2"
"github.com/go-ozzo/ozzo-routing/v2/file"
)
router := routing.NewRouter()
// serve index file
router.Get("/", file.Content("ui/index.html"))
// serve files under the "ui" subdirectory
router.Get("/*", file.Server(file.PathMap{
"/": "/ui/",
}))
ozzo-routing comes with a few commonly used handlers in its subpackages:
Handler name | Description |
---|---|
access.Logger | records an entry for every incoming request |
auth.Basic | provides authentication via HTTP Basic |
auth.Bearer | provides authentication via HTTP Bearer |
auth.Query | provides authentication via token-based query parameter |
auth.JWT | provides JWT-based authentication |
content.TypeNegotiator | supports content negotiation by response types |
content.LanguageNegotiator | supports content negotiation by accepted languages |
cors.Handler | implements the CORS (Cross Origin Resource Sharing) specification from the W3C |
fault.Recovery | recovers from panics and handles errors returned by handlers |
fault.PanicHandler | recovers from panics happened in the handlers |
fault.ErrorHandler | handles errors returned by handlers by writing them in an appropriate format to the response |
file.Server | serves the files under the specified folder as response content |
file.Content | serves the content of the specified file as the response |
slash.Remover | removes the trailing slashes from the request URL and redirects to the proper URL |
The following code shows how these handlers may be used:
import (
"log"
"net/http"
"github.com/go-ozzo/ozzo-routing/v2"
"github.com/go-ozzo/ozzo-routing/v2/access"
"github.com/go-ozzo/ozzo-routing/v2/slash"
"github.com/go-ozzo/ozzo-routing/v2/fault"
)
router := routing.New()
router.Use(
access.Logger(log.Printf),
slash.Remover(http.StatusMovedPermanently),
fault.Recovery(log.Printf),
)
...
The following third-party handlers are specifically designed for ozzo-routing:
Handler name | Description |
---|---|
jwt.JWT | supports JWT Authorization |
ozzo-routing also provides adapters to support using third-party http.HandlerFunc
or http.Handler
handlers. For example,
router := routing.New()
// using http.HandlerFunc
router.Use(routing.HTTPHandlerFunc(http.NotFound))
// using http.Handler
router.Use(routing.HTTPHandler(http.NotFoundHandler))
Last updated on Jan 6, 2017
Ozzo-routing is very fast, thanks to the radix tree data structure and the usage of sync.Pool
(the idea was originally from HttpRouter and Gin). The following table (by running go-http-routing-benchmark) shows how ozzo-routing compares with Gin, HttpRouter, and Martini in performance.
BenchmarkOzzo_GithubAll 50000 37989 ns/op 0 B/op 0 allocs/op
BenchmarkEcho_GithubAll 20000 91003 ns/op 6496 B/op 203 allocs/op
BenchmarkGin_GithubAll 50000 26717 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GithubAll 50000 36052 ns/op 13792 B/op 167 allocs/op
BenchmarkMartini_GithubAll 300 4162283 ns/op 228216 B/op 2483 allocs/op
BenchmarkOzzo_GPlusAll 1000000 1732 ns/op 0 B/op 0 allocs/op
BenchmarkEcho_GPlusAll 300000 4523 ns/op 416 B/op 13 allocs/op
BenchmarkGin_GPlusAll 1000000 1171 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_GPlusAll 1000000 1533 ns/op 640 B/op 11 allocs/op
BenchmarkMartini_GPlusAll 20000 75634 ns/op 14448 B/op 165 allocs/op
BenchmarkOzzo_ParseAll 500000 3318 ns/op 0 B/op 0 allocs/op
BenchmarkEcho_ParseAll 200000 7336 ns/op 832 B/op 26 allocs/op
BenchmarkGin_ParseAll 1000000 2075 ns/op 0 B/op 0 allocs/op
BenchmarkHttpRouter_ParseAll 1000000 2034 ns/op 640 B/op 16 allocs/op
BenchmarkMartini_ParseAll 10000 122002 ns/op 25600 B/op 276 allocs/op
ozzo-routing has referenced many popular routing frameworks, including Express, Martini, httprouter, and gin.
Author: Go-ozzo
Source Code: https://github.com/go-ozzo/ozzo-routing
License: MIT license
1654241474
gorouter
xujiajun/gorouter
is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.
I wanted a simple and fast HTTP GO router, which supports regexp. I prefer to support regexp is because otherwise it will need the logic to check the URL parameter type, thus increasing the program complexity. So I did some searching on Github and found the wonderful julienschmidt/httprouter
: it is very fast,unfortunately it does not support regexp. Later I found out about gorilla/mux
: it is powerful as well,but a written benchmark shows me that it is somewhat slow. So I tried to develop a new router which both supports regexp and should be fast. Finally I did it and named xujiajun/gorouter
. By the way, this is my first GO open source project. It may be the fastest GO HTTP router which supports regexp, and regarding its performance please refer to my latest Benchmarks.
go get -u github.com/xujiajun/gorouter
package main
import (
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
func main() {
mux := gorouter.New()
mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
func main() {
mux := gorouter.New()
//url parameters match
mux.GET("/user/:id", func(w http.ResponseWriter, r *http.Request) {
//get one URL parameter
id := gorouter.GetParam(r, "id")
//get all URL parameters
//id := gorouter.GetAllParams(r)
//fmt.Println(id)
w.Write([]byte("match user/:id ! get id:" + id))
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
func main() {
mux := gorouter.New()
//url regex match
mux.GET("/user/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("match user/{id:[0-9]+} !"))
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"fmt"
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
func usersHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "/api/users")
}
func main() {
mux := gorouter.New()
mux.Group("/api").GET("/users", usersHandler)
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"fmt"
"net/http"
"github.com/xujiajun/gorouter"
)
func main() {
mux := gorouter.New()
routeName1 := "user_event"
mux.GETAndName("/users/:user/events", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("/users/:user/events"))
}, routeName1)
routeName2 := "repos_owner"
mux.GETAndName("/repos/{owner:\\w+}/{repo:\\w+}", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("/repos/{owner:\\w+}/{repo:\\w+}"))
}, routeName2)
params := make(map[string]string)
params["user"] = "xujiajun"
fmt.Println(mux.Generate(http.MethodGet, routeName1, params)) // /users/xujiajun/events <nil>
params = make(map[string]string)
params["owner"] = "xujiajun"
params["repo"] = "xujiajun_repo"
fmt.Println(mux.Generate(http.MethodGet, routeName2, params)) // /repos/xujiajun/xujiajun_repo <nil>
}
package main
import (
"fmt"
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
func notFoundFunc(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "404 page !!!")
}
func main() {
mux := gorouter.New()
mux.NotFoundFunc(notFoundFunc)
mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"fmt"
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
func main() {
mux := gorouter.New()
mux.PanicHandler = func(w http.ResponseWriter, req *http.Request, err interface{}) {
w.WriteHeader(http.StatusInternalServerError)
fmt.Println("err from recover is :", err)
fmt.Fprint(w, "received a panic")
}
mux.GET("/panic", func(w http.ResponseWriter, r *http.Request) {
panic("panic")
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"fmt"
"log"
"net/http"
"github.com/xujiajun/gorouter"
)
type statusRecorder struct {
http.ResponseWriter
status int
}
func (rec *statusRecorder) WriteHeader(code int) {
rec.status = code
rec.ResponseWriter.WriteHeader(code)
}
//https://upgear.io/blog/golang-tip-wrapping-http-response-writer-for-middleware/
func withStatusRecord(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rec := statusRecorder{w, http.StatusOK}
next.ServeHTTP(&rec, r)
log.Printf("response status: %v\n", rec.status)
}
}
func notFoundFunc(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
fmt.Fprint(w, "Not found page !")
}
func withLogging(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Logged connection from %s", r.RemoteAddr)
next.ServeHTTP(w, r)
}
}
func withTracing(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Tracing request for %s", r.RequestURI)
next.ServeHTTP(w, r)
}
}
func main() {
mux := gorouter.New()
mux.NotFoundFunc(notFoundFunc)
mux.Use(withLogging, withTracing, withStatusRecord)
mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello world"))
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
package main
import (
"log"
"net/http"
"os"
"github.com/xujiajun/gorouter"
)
//ServeFiles serve static resources
func ServeFiles(w http.ResponseWriter, r *http.Request) {
wd, err := os.Getwd()
if err != nil {
log.Fatal(err)
}
dir := wd + "/examples/serveStaticFiles/files"
http.StripPrefix("/files/", http.FileServer(http.Dir(dir))).ServeHTTP(w, r)
}
func main() {
mux := gorouter.New()
mux.GET("/hi", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
})
//defined prefix
mux2 := mux.Group("/files")
//http://127.0.0.1:8181/files/demo.txt
//will match
mux2.GET("/{filename:[0-9a-zA-Z_.]+}", func(w http.ResponseWriter, r *http.Request) {
ServeFiles(w, r)
})
//http://127.0.0.1:8181/files/a/demo2.txt
//http://127.0.0.1:8181/files/a/demo.txt
//will match
mux2.GET("/{fileDir:[0-9a-zA-Z_.]+}/{filename:[0-9a-zA-Z_.]+}", func(w http.ResponseWriter, r *http.Request) {
ServeFiles(w, r)
})
log.Fatal(http.ListenAndServe(":8181", mux))
}
Detail see serveStaticFiles example
The syntax here is modeled after julienschmidt/httprouter and gorilla/mux
Syntax | Description | Example |
---|---|---|
:name | named parameter | /user/:name |
{name:regexp} | named with regexp parameter | /user/{name:[0-9a-zA-Z]+} |
:id | named with regexp parameter | /user/:id |
And :id
is short for {id:[0-9]+}
, :name
are short for {name:[0-9a-zA-Z_]+}
if use default regex checks unless you know what you're doing
the benchmarks code for gorouter be found in the gorouter-bench repository.
go test -bench=.
Thanks the author of httprouter: @julienschmidt give me advise about benchmark issues/24
Given some routing matching syntax differences, divide GithubAPI into two groups:
BenchmarkBeegoMuxRouterWithGithubAPI-8 10000 142398 ns/op 134752 B/op 1038 allocs/op
BenchmarkBoneRouterWithGithubAPI-8 1000 2104486 ns/op 720160 B/op 8620 allocs/op
BenchmarkTrieMuxRouterWithGithubAPI-8 20000 80845 ns/op 65856 B/op 537 allocs/op
BenchmarkHttpRouterWithGithubAPI-8 50000 30169 ns/op 13792 B/op 167 allocs/op
BenchmarkGoRouter1WithGithubAPI-8 30000 57793 ns/op 13832 B/op 406 allocs/op
BenchmarkGoRouter2WithGithubAPI2-8 30000 57613 ns/op 13832 B/op 406 allocs/op
BenchmarkChiRouterWithGithubAPI2-8 10000 143224 ns/op 104436 B/op 1110 allocs/op
BenchmarkMuxRouterWithGithubAPI2-8 300 4450731 ns/op 61463 B/op 995 allocs/op
➜ gorouter git:(master) go test -bench=.
GithubAPI Routes: 203
GithubAPI2 Routes: 203
BeegoMuxRouter: 111072 Bytes
BoneRouter: 100992 Bytes
ChiRouter: 71512 Bytes
HttpRouter: 37016 Bytes
trie-mux: 131128 Bytes
MuxRouter: 1378496 Bytes
GoRouter1: 83824 Bytes
GoRouter2: 85584 Bytes
goos: darwin
goarch: amd64
pkg: github.com/xujiajun/gorouter
BenchmarkBeegoMuxRouterWithGithubAPI-8 10000 142398 ns/op 134752 B/op 1038 allocs/op
BenchmarkBoneRouterWithGithubAPI-8 1000 2104486 ns/op 720160 B/op 8620 allocs/op
BenchmarkTrieMuxRouterWithGithubAPI-8 20000 80845 ns/op 65856 B/op 537 allocs/op
BenchmarkHttpRouterWithGithubAPI-8 50000 30169 ns/op 13792 B/op 167 allocs/op
BenchmarkGoRouter1WithGithubAPI-8 30000 57793 ns/op 13832 B/op 406 allocs/op
BenchmarkGoRouter2WithGithubAPI2-8 30000 57613 ns/op 13832 B/op 406 allocs/op
BenchmarkChiRouterWithGithubAPI2-8 10000 143224 ns/op 104436 B/op 1110 allocs/op
BenchmarkMuxRouterWithGithubAPI2-8 300 4450731 ns/op 61463 B/op 995 allocs/op
PASS
ok github.com/xujiajun/gorouter 15.918s
Performance (xujiajun/gorouter,julienschmidt/httprouter and teambition/trie-mux are fast)
Memory Consumption (xujiajun/gorouter and julienschmidt/httprouter are fewer)
Features (julienschmidt/httprouter not supports regexp,but others support it)
if you want a high performance router which supports regexp, maybe xujiajun/gorouter is good choice.
if you want a high performance router which not supports regexp, maybe julienschmidt/httprouter is good choice.
In the end, as julienschmidt said performance can not be the (only) criterion for choosing a router. Play around a bit with some of the routers, and choose the one you like best.
If you'd like to help out with the project. You can put up a Pull Request. Thanks to all contributors.
This package is inspired by the following:
Author: Xujiajun
Source Code: https://github.com/xujiajun/gorouter
License: MIT license
1653482116
Package tome was designed to paginate simple RESTful APIs.
go get -u github.com/cyruzin/tome
To get started, import the tome
package and initiate the pagination:
import "github.com/cyruzin/tome"
// Post type is a struct for a single post.
type Post struct {
Title string `json:"title"`
Body string `json:"body"`
}
// Posts type is a struct for multiple posts.
type Posts []*Post
// Result type is a struct of posts with pagination.
type Result struct {
Data *Posts `json:"data"`
*tome.Chapter
}
// GetPosts gets the latest 10 posts with pagination.
func GetPosts(w http.ResponseWriter, r *http.Request) {
// Creating a tome chapter with links.
chapter := &tome.Chapter{
// Setting base URL.
BaseURL: "http://yourapi.com/v1/posts",
// Enabling link results.
Links: true,
// Page that you captured in params inside you handler.
NewPage: 2,
// Total of pages, this usually comes from a SQL query total rows result.
TotalResults: model.GetPostsTotalResults(),
}
// Paginating the results.
if err := chapter.Paginate(); err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) // Setting status 422.
json.NewEncoder(w).Encode(err) // Returning JSON with an error.
return
}
// Here you pass the offset and limit.
database, err := model.GetPosts(chapter.Offset, chapter.Limit)
if err != nil {
w.WriteHeader(http.StatusUnprocessableEntity) // Setting status 422.
json.NewEncoder(w).Encode(err) // Returning JSON with an error.
return
}
// Mocking results with pagination.
res := &Result{Data: database, Chapter: chapter}
w.WriteHeader(http.StatusOK) // Setting status 200.
json.NewEncoder(w).Encode(res) // Returning success JSON.
}
Output:
{
"data": [
{
"title": "What is Lorem Ipsum?",
"body": "Lorem Ipsum is simply dummy text of the printing and..."
},
{
"title": "Why do we use it?",
"body": "It is a long established fact that a reader will be..."
}
],
"base_url": "http://yourapi.com/v1/posts",
"next_url": "http://yourapi.com/v1/posts?page=3",
"prev_url": "http://yourapi.com/v1/posts?page=1",
"per_page": 10,
"current_page": 2,
"last_page": 30,
"total_results": 300
}
Without links:
Iterations | ns/op | B/op | allocs/op |
---|---|---|---|
200000000 | 7.80 | 0 | 0 |
With links:
Iterations | ns/op | B/op | allocs/op |
---|---|---|---|
10000000 | 133 | 96 | 2 |
Author: Cyruzin
Source Code: https://github.com/cyruzin/tome
License: MIT license
1652513100
Libraries for building RESTful APIs.
Author: vinta
Source Code: https://github.com/vinta/awesome-python
License: View license
1652493960
sandman2
[ ~ Dependencies scanned by PyUp.io ~ ]
sandman2
automagically generates a RESTful API service from your existing database, without requiring you to write a line of code. Simply point sandman2
to your database, add salt for seasoning, and voila!, a fully RESTful API service with hypermedia support starts running, ready to accept HTTP requests.
This is a big deal. It means every single database you interact with, from the SQLite database that houses your web browser's data up to your production PostgreSQL server can be endowed with a REST API and accessed programmatically, using any number of HTTP client libraries available in every language. sandman2
frees your data.
For developers:
Imagine you're working for AnonymousCorp and need to access Group Y's data, which is presented to you through some horrible API or GUI. Wouldn't it be nice if you could just interact with that database through a REST API?
More than that, imagine if you could interact with the database through a REST API and no one had to write any code. Not you. Not Group Y. No one. That means no boilerplate ORM code, no database connection logic. Nothing. sandman2
can be run as a command-line tool (sandman2ctl
) that just takes your database information as parameters and connects to it, introspects the schema, generates a RESTful API, and starts the server.
sandman
, the precursor to sandman2
, is no longer being maintained. sandman
had almost identical functionality but had an architecture that reflected the capabilities of the underlying ORM, SQLAlchemy. As of the 0.9
release, SQLAlchemy introduced the automap
construct. This fundamentally changed the way that sandman
could interact with the underlying database in a way that greatly simplified things. All that was needed was the actual effort to rewrite sandman
from scratch...
sandman2
has since surpassed the functionality of the original sandman
and the latter should be considered deprecated/obsolete.
Install sandman2
using pip
: $ pip install sandman2
. This provides the script sandman2ctl
, which just takes the database URI string, described here. For example, to connect to a SQLite database in the same directory you're running the script, you would run:
$ sandman2ctl sqlite+pysqlite:///database_file_name
To connect to a PostgreSQL database, make sure you install a driver like psycopg2
using pip
, then use the following connection string:
$ sandman2ctl postgresql+psycopg2://scott:tiger@localhost/mydatabase
Again, see the SQLAlchemy documentation for a more comprehensive discussion of connection strings.
sandman2
supports all databases that the underlying ORM, SQLAlchemy, supports. Presently, that includes:
Third-party packages extend support to:
One of the best things about the original sandman
was the Admin Interface. Not only does sandman2
include the Admin Interface, but it modernizes it as well. The layout has been greatly improved, especially when dealing with larger numbers of tables. All of the original functionality of the Admin Interface remains unchanged.
Here's a shot of the new look:
If sandman2ctl
doesn't give you fine-grained enough control over your REST endpoints, or you'd like to restrict the set of tables made available via sandman2ctl
, you can easily integrate sandman2
into your application. See the documentation for more info.
sandman2
under Dockersandman2
has an official docker image at Docker Hub. Simply docker pull jeffknupp/sandman2
to get the latest version. It supports the most popular database engines, but not all that sandman2
currently natively supports. If you'd like to see support for your RDBMS, either add a pull request on this repo (if possible) or create a new issue with the details of your database's Python driver.
Here's how one would run sandman2
to connect to a PostgreSQL database running on one's host machine (i.e. not a remote database, which is far simpler) under Docker (on a Mac, explained below):
$ docker pull jeffknupp/sandman2
$ docker run -d -e DB_TYPE=postgres -e DB_DRIVER=psycopg2 -e USERNAME=jknupp -e DB_HOST=host.docker.internal -e DATABASE=jknupp -e DB_PORT=5432 -p 9000:5000 sandman2
$ curl localhost:9000/meta
or open a browser to http://localhost:9000/admin/
Note, DB_HOST=host.docker.internal
is only necessary for databases that reside on the host system (and the value only works on macOS). To connect to a database on a remote machine, simply replace that value with the machine's IP or hostname.
Here are the parameters available to specify your connection information and their meaning:
$DB_TYPE
- The type of RDBMS to connect to (e.g. postgres
or mysql
)$DB_DRIVER
- The name of the Python library to use as a driver (e.g. psycopg2
or pymysql
)$USERNAME
- Database username$PASSWORD
- Database password$DB_HOST
- Database IP or hostname$DB_PORT
- Database port$DATABASE
- Name of database to connect toPass each value separately to the docker run
command with -e <VARIABLE>=<VALUE>
. Not all are required, but which ones are required differs based on your target RDBMS.
Author: jeffknupp
Source Code: https://github.com/jeffknupp/sandman2
License: Apache-2.0 License
1652434620
Flask-RESTful
Flask-RESTful provides the building blocks for creating a great REST API.
Flask-RESTful is an extension for Flask that adds support for quickly building REST APIs. It is a lightweight abstraction that works with your existing ORM/libraries. Flask-RESTful encourages best practices with minimal setup. If you are familiar with Flask, Flask-RESTful should be easy to pick up.
You'll find the user guide and all documentation here
Author: flask-restful
Source Code: https://github.com/flask-restful/flask-restful
License: BSD-3-Clause License
1652315160
RESTit
A Go micro-framework to help writing RESTful API integration test
Package RESTit provides helps to those who want to write an integration test program for their JSON-based RESTful APIs.
The aim is to make these integration readable highly re-usable, and yet easy to modify.
Different version of RESTit library will go under different directory.
This library is under major rewrite. The old version (version 1) is consider API stable and will only receive bug fixes. Development version (version 2) is under active development and will have new features (which may break API).
For more information, please read the README.md of these version:
To report issue, please visit the issue tracker.
And of course, patches and pull requests are most welcome.
Author: Go-restit
Source Code: https://github.com/go-restit/restit
License: GPL-3.0 license