ãã®èšäºã§ã¯ãREST APIãããžã§ã¯ãã®æçšã§é¢é£ããä»æ§ã®äœæãšãµããŒãã«çŠç¹ãåœãŠãŸããããã«ãããå€ãã®äœåãªã³ãŒããç¯çŽããããããžã§ã¯ãå
šäœã®æŽåæ§ãä¿¡é Œæ§ãéææ§ã倧å¹
ã«åäžããŸãã
RESTful APIãšã¯äœã§ããïŒ
ããã¯ç¥è©±ã§ãã
çå£ã«ãããªãã®ãããžã§ã¯ããRESTful APIãæã£ãŠãããšæããªããééããªãééãã§ãã RESTfulã®èãæ¹ã¯ãRESTã¹ã¿ã€ã«ã§èšè¿°ãããã¢ãŒããã¯ãã£ã®ã«ãŒã«ãšå¶éã«ãã¹ãŠã®ç¹ã§æºæ ããAPIãæ§ç¯ããããšã§ãããå®éã®ç¶æ³ã§ã¯ããã¯ã»ãšãã©äžå¯èœã§ãã
äžæ¹ã§ãRESTã«ã¯ææ§ã§ææ§ãªå®çŸ©ãå€ãããŸãã ããšãã°ãHTTPã¡ãœããããã³ã¹ããŒã¿ã¹ã³ãŒãã®ãã£ã¯ã·ã§ããªããã®äžéšã®çšèªã¯ãå®éã«ã¯æå³ãããç®çã«äœ¿çšãããããããã®å€ãã¯ãŸã£ãã䜿çšãããŸããã
äžæ¹ãRESTã¯éåžžã«å€ãã®å¶éãäœæããŸãã ããšãã°ãçŸå®äžçã§ã®ãªãœãŒã¹ã®ã¢ãããã¯ãªäœ¿çšã¯ãã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã§äœ¿çšãããAPIã«ãšã£ãŠåççã§ã¯ãããŸããã ãªã¯ãšã¹ãéã§ç¶æ
ãä¿åããããšãå®å
šã«æåŠããããšã¯ãå€ãã®APIã§äœ¿çšããããŠãŒã¶ãŒã»ãã·ã§ã³ã¡ã«ããºã ã®çŠæ¢ã§ãã
ãããããã¹ãŠãããã»ã©æªãããã§ã¯ãããŸããïŒ
ãªãREST APIä»æ§ãå¿
èŠãªã®ã§ããïŒ
ãããã®æ¬ ç¹ã«ãé¢ããããåççãªã¢ãããŒãã§ãRESTã¯äŸç¶ãšããŠæ¬åœã«ã¯ãŒã«ãªAPIãèšèšããããã®åªããåºç€ã®ãŸãŸã§ãã ãã®ãããªAPIã«ã¯ãå
éšã®åäžæ§ãæ確ãªæ§é ã䟿å©ãªããã¥ã¡ã³ããããã³åªããåäœãã¹ãã®ã«ãã¬ããžãå¿
èŠã§ãã ãããã¯ãã¹ãŠãAPIã®å質ä»æ§ãéçºããããšã§å®çŸã§ããŸãã
ã»ãšãã©ã®å ŽåãREST API ä»æ§ã¯ãã®ããã¥ã¡ã³ãã«é¢é£ä»ããããŠããŸã ã æåã®APIïŒAPIã®æ£åŒãªèª¬æïŒãšã¯ç°ãªããããã¥ã¡ã³ãã¯äººã
ãèªãããšãç®çãšããŠããŸããããšãã°ãAPIã䜿çšããã¢ãã€ã«ãŸãã¯Webã¢ããªã±ãŒã·ã§ã³ã®éçºè
ã§ãã
ãã ããå®éã«ããã¥ã¡ã³ããäœæããããšã«å ããŠãé©åãªAPIã®èª¬æã«ã¯å€ãã®å©ç¹ããããŸãã ä»æ§ã®æèœãªäœ¿çšã䜿çšããŠã次ã®ããšãã§ããæ¹æ³ã®äŸãå
±æãããèšäºã§ïŒ
- åäœãã¹ããããã·ã³ãã«ã§ä¿¡é Œæ§ã®é«ããã®ã«ããŸãã
- å
¥åããŒã¿ã®ååŠçãšæ€èšŒãæ§æããŸãã
- ã·ãªã¢ã«åãèªååããå¿çã®æŽåæ§ã確ä¿ããŸãã
- ããã«éçåä»ããå©çšããŸãã
Openapi
çŸåšREST APIãèšè¿°ããããã«äžè¬çã«åãå
¥ããããŠãã圢åŒã¯OpenAPIã§ãããããã¯SwaggerãšããŠãç¥ãããŠããŸãã ãã®ä»æ§ã¯ãJSONãŸãã¯YAML圢åŒã®åäžãã¡ã€ã«ã§ããã3ã€ã®ã»ã¯ã·ã§ã³ã§æ§æãããŠããŸãã
- APIã®ååã説æãããŒãžã§ã³ãããã³è¿œå æ
å ±ãå«ãããããŒã
- èå¥åãHTTPã¡ãœããããã¹ãŠã®å
¥åãã©ã¡ãŒã¿ãŒãããã³å¿çæ¬æã®ã³ãŒããšåœ¢åŒãå«ããã¹ãŠã®ãªãœãŒã¹ã®èª¬æãšãå®çŸ©ãžã®ãªã³ã¯ã
- å
¥åãã©ã¡ãŒã¿ãŒãšå¿çã®äž¡æ¹ã§äœ¿çšã§ããJSONã¹ããŒã圢åŒã®ãªããžã§ã¯ãã®ãã¹ãŠã®å®çŸ©ã
OpenAPIã«ã¯é倧ãªæ¬ ç¹ããããŸã- æ§é ã®è€éããšãå€ãã®å Žåãåé·æ§ ã å°ããªãããžã§ã¯ãã®å Žåãä»æ§JSONãã¡ã€ã«ã®å
容ã¯ããã«æ°åè¡ã«éããå¯èœæ§ããããŸãã ãã®åœ¢åŒã§ãã®ãã¡ã€ã«ãæåã§ç®¡çããããšã¯ã§ããŸããã ããã¯ãAPIã®é²åã«åãããŠææ°ã®ä»æ§ãç¶æãããšããèãã«å¯Ÿããæ·±å»ãªè
åšã§ãã
APIãèšè¿°ããçµæã®OpenAPIä»æ§ãäœæã§ããããžã¥ã¢ã«ãšãã£ã¿ãŒãå€æ°ãããŸãã åæ§ã«ãè¿œå ã®ãµãŒãã¹ãšã¯ã©ãŠããœãªã¥ãŒã·ã§ã³ã¯ãããšãã°Swagger ã Apiary ã Stoplight ã Restletãªã©ã«åºã¥ããŠããŸãã
ããããç§ã«ãšã£ãŠã¯ãä»æ§ããã°ããç·šéããŠã³ãŒãèšè¿°ããã»ã¹ãšçµã¿åãããããšãé£ããããããã®ãããªãµãŒãã¹ã¯ããŸã䟿å©ã§ã¯ãããŸããã§ããã ãã1ã€ã®ãã€ãã¹ç¹ã¯ãç¹å®ã®åãµãŒãã¹ã®æ©èœã»ãããžã®äŸåã§ãã ããšãã°ãã¯ã©ãŠããµãŒãã¹ã ãã§æ¬æ Œçãªåäœãã¹ããå®è£
ããããšã¯ã»ãšãã©äžå¯èœã§ãã ãšã³ããã€ã³ãçšã®ã³ãŒãçæãããã«ã¯ããã©ã°ãã®äœæã¯ãéåžžã«å¯èœæ§ãããããã«èŠããŸãããå®éã«ã¯å®éã«ã¯åœ¹ã«ç«ã¡ãŸããã
Tinyspec
ãã®èšäºã§ã¯ããã€ãã£ãREST APIèšè¿°åœ¢åŒ-tinyspecã«åºã¥ãäŸã䜿çšããŸãã 圢åŒã¯ããããžã§ã¯ãã§äœ¿çšããããšã³ããã€ã³ããšããŒã¿ã¢ãã«ãçŽæçãªæ§æã§èšè¿°ããå°ããªãã¡ã€ã«ã§ãã ãã¡ã€ã«ã¯ã³ãŒãã®é£ã«ä¿åãããŸããããã«ããããã¡ã€ã«ã®äœæããã»ã¹ã§ãã¡ã€ã«ã確èªããŠç·šéããããšãã§ããŸãã åæã«ãtinyspecã¯ããããžã§ã¯ãã§ããã«äœ¿çšã§ããæ¬æ ŒçãªOpenAPIã«èªåçã«ã³ã³ãã€ã«ãããŸãã æ£ç¢ºãªæ¹æ³ã説æããæãæ¥ãŸããã
ãã®èšäºã§ã¯ãNode.jsïŒkoaãexpressïŒãšRuby on Railsã®äŸã瀺ããŸããããããã®ãã©ã¯ãã£ã¹ã¯PythonãPHPãJavaãªã©ã®ã»ãšãã©ã®ãã¯ãããžãŒã«é©çšãããŸãã
ä»æ§ãéåžžã«åœ¹ç«ã€å Žå
1.ãšã³ããã€ã³ãã®åäœãã¹ã
è¡åé§åéçºïŒBDDïŒã¯ ãREST APIã®éçºã«æé©ã§ãã åäœãã¹ããèšè¿°ããæã䟿å©ãªæ¹æ³ã¯ãåã
ã®ã¯ã©ã¹ãã¢ãã«ãã³ã³ãããŒã©ãŒã§ã¯ãªããç¹å®ã®ãšã³ããã€ã³ãçšã§ãã åãã¹ãã§ã¯ãå®éã®HTTPèŠæ±ããšãã¥ã¬ãŒããããµãŒããŒã®å¿çã確èªããŸãã Node.jsã«ã¯ããã¹ããªã¯ãšã¹ãããšãã¥ã¬ãŒãããããã®ã¹ãŒããŒãã¹ããšchai-httpããããRuby on Railsã«æèŒãããŠããŸãã
User
ã¹ããŒããšããã¹ãŠã®ãŠãŒã¶ãŒãè¿ãGET /users
ãšã³ããã€ã³ãããããšããŸãã ããã説æããtinyspecæ§æã¯æ¬¡ã®ãšããã§ãã
- User.models.tinyspecãã¡ã€ã«ïŒ
User {name, isAdmin: b, age?: i}
- users.endpoints.tinyspecãã¡ã€ã«ïŒ
GET /users => {users: User[]}
ããããã¹ãã®å€èŠ³ã§ãã
Node.js
describe('/users', () => { it('List all users', async () => { const { status, body: { users } } = request.get('/users'); expect(status).to.equal(200); expect(users[0].name).to.be('string'); expect(users[0].isAdmin).to.be('boolean'); expect(users[0].age).to.be.oneOf(['boolean', null]); }); });
Ruby on Rails
describe 'GET /users' do it 'List all users' do get '/users' expect_status(200) expect_json_types('users.*', { name: :string, isAdmin: :boolean, age: :integer_or_null, }) end end
ãµãŒããŒã®å¿ç圢åŒãèšè¿°ããä»æ§ãããå Žåããã¹ããåçŽåãã ãã®ä»æ§ã«å¯ŸããŠå¿çããã§ãã¯ããã ãã§ãã ãããè¡ãã«ã¯ãtinyspecã¢ãã«ãOpenAPIå®çŸ©ã«å€æããããšããäºå®ãå©çšããŸããOpenAPIå®çŸ©ã¯JSONã¹ããŒã圢åŒã«å¯Ÿå¿ããŸãã
JSã®ãªãã©ã«ãªããžã§ã¯ã ïŒãŸãã¯Rubyã®Hash
ãPythonã®dict
ãPHPã®é£æ³é
å ãããã«ã¯Javaã®Map
ïŒã¯ãJSONã¹ããŒã ãžã®æºæ ããã¹ãã§ããŸãã ãŸãããã¬ãŒã ã¯ãŒã¯ããã¹ãããããã®é©åãªãã©ã°ã€ã³ããããŸããããšãã°ã RSpecã®jest-ajv ïŒnpmïŒã chai-ajv-json-schema ïŒnpmïŒã json_matchers ïŒrubygemïŒã§ãã
ã¹ããŒã ã䜿çšããåã«ããããããããžã§ã¯ãã«æ¥ç¶ããå¿
èŠããããŸãã æåã«ãtinyspecã«åºã¥ããŠopenapi.jsonä»æ§ãã¡ã€ã«ãçæããŸãïŒãã®ã¢ã¯ã·ã§ã³ã¯ãåãã¹ãã®å®è¡åã«èªåçã«å®è¡ã§ããŸãïŒã
tinyspec -j -o openapi.json
Node.js
ããã§ãåä¿¡ããJSONããããžã§ã¯ãã§äœ¿çšããŠãããããdefinitions
ããŒãååŸã§ããŸããããã«ã¯ããã¹ãŠã®JSONã¹ããŒã ãå«ãŸããŠããŸãã ã¹ããŒã ã«ã¯çžäºåç
§ïŒ $ref
ïŒãå«ããããšãã§ããŸãããããã£ãŠããã¹ããããã¹ããŒã ïŒããšãã°ã Blog {posts: Post[]}
ïŒãããå Žåãæ€èšŒã§äœ¿çšããããã«ãããããå±éãããå¿
èŠããããŸãã ãã®ããã«ã json-schema-deref-sync ïŒnpmïŒã䜿çšããŸãã
import deref from 'json-schema-deref-sync'; const spec = require('./openapi.json'); const schemas = deref(spec).definitions; describe('/users', () => { it('List all users', async () => { const { status, body: { users } } = request.get('/users'); expect(status).to.equal(200);
Ruby on Rails
json_matchers
ã¯$ref
ãªã³ã¯ãåŠçã§ããŸãããç¹å®ã®æ¹æ³ã§ãã¡ã€ã«ã·ã¹ãã ã«ã¹ããŒã ãæã€åå¥ã®ãã¡ã€ã«ãå¿
èŠãªã®ã§ãæåã«swagger.json
ãå€ãã®å°ããªãã¡ã€ã«ã«ãåå²ãããswagger.json
ããããŸãïŒè©³çŽ°ã¯ãã¡ã ïŒã
ãã®åŸã次ã®ããã«ãã¹ããèšè¿°ã§ããŸãã
describe 'GET /users' do it 'List all users' do get '/users' expect_status(200) expect(result[:users][0]).to match_json_schema('User') end end
泚ïŒãã®æ¹æ³ã§ãã¹ããæžãããšã¯éåžžã«äŸ¿å©ã§ãã ç¹ã«ãIDEããã¹ãã®å®è¡ãšãããã°ïŒWebStormãRubyMineãVisual Studioãªã©ïŒããµããŒãããŠããå Žåã ãããã£ãŠãä»ã®ãœãããŠã§ã¢ã¯ãŸã£ãã䜿çšã§ãããAPIéçºãµã€ã¯ã«å
šäœã3ã€ã®é£ç¶ããã¹ãããã«ççž®ãããŸãã
- ä»æ§èšèšïŒäŸïŒtinyspecïŒ;
- è¿œå /å€æŽããããšã³ããã€ã³ãã®ãã¹ãã®å®å
šãªã»ãããäœæããŸãã
- ãã¹ãŠã®ãã¹ããæºããã³ãŒãã®éçºã
2.å
¥åã®æ€èšŒ
OpenAPIã¯ãå¿çã ãã§ãªãå
¥åããŒã¿ã®åœ¢åŒãèšè¿°ããŸãã ããã«ããããªã¯ãšã¹ãäžã«ãŠãŒã¶ãŒããåä¿¡ããããŒã¿ãæ€èšŒã§ããŸãã
ãŠãŒã¶ãŒããŒã¿ã®æŽæ°ãšãå€æŽå¯èœãªãã¹ãŠã®ãã£ãŒã«ãã説æãã次ã®ä»æ§ããããšããŸãã
以åããã¹ãå
ã®æ€èšŒçšã®ãã©ã°ã€ã³ãæ€èšããŸããããããäžè¬çãªå Žåã«ã¯ajv ïŒnpmïŒããã³json-schema ïŒrubygemïŒæ€èšŒã¢ãžã¥ãŒã«ããããŸãããããã䜿çšããŠæ€èšŒä»ãã®ã³ã³ãããŒã©ãŒãäœæããŸãããã
Node.jsïŒKoaïŒ
ããã¯ãExpressã®åŸç¶ã§ããKoaã®äŸã§ãããExpressã®å Žåãã³ãŒãã¯åæ§ã«ãªããŸãã
import Router from 'koa-router'; import Ajv from 'ajv'; import { schemas } from './schemas'; const router = new Router();
ãã®äŸã§ã¯ãå
¥åããŒã¿ãä»æ§ãæºãããŠããªãå ŽåããµãŒããŒã¯ã¯ã©ã€ã¢ã³ãã«500 Internal Server Error
å¿çãè¿ããŸãã ãããé²ãããã«ãããªããŒã¿ãšã©ãŒãã€ã³ã¿ãŒã»ããããç¬èªã®åçãäœæã§ããŸããããã«ã¯ããã¹ãã«åæ Œããªãã£ãç¹å®ã®ãã£ãŒã«ãã«é¢ãã詳现æ
å ±ãå«ãŸããä»æ§ã«ãæºæ ããŸãã
FieldsValidationError
ãã¡ã€ã«ã«FieldsValidationError
ã¢ãã«ã®èª¬æãè¿œå ããŸãã
Error {error: b, message} InvalidField {name, message} FieldsValidationError < Error {fields: InvalidField[]}
ãããŠä»ãç§ãã¡ã¯ããããšã³ããã€ã³ãã®å¯èœãªçãã®1ã€ãšããŠç€ºããŸãïŒ
PATCH /users/:id {user: UserUpdate} => 200 {success: b} => 422 FieldsValidationError
ãã®ã¢ãããŒãã«ãããã¯ã©ã€ã¢ã³ãããåä¿¡ããäžæ£ãªããŒã¿ã䜿çšããŠãšã©ãŒã®åœ¢æãæ£ããããšãæ€èšŒããåäœãã¹ããäœæã§ããŸãã
3.ã¢ãã«ã®ã·ãªã¢ã«å
ã»ãšãã©ãã¹ãŠã®ææ°ã®ãµãŒããŒãã¬ãŒã ã¯ãŒã¯ã¯ã äœããã®æ¹æ³ã§ORMã䜿çšããŸãã ã€ãŸããã·ã¹ãã å
ã®APIã§äœ¿çšãããã»ãšãã©ã®ãªãœãŒã¹ã¯ãã¢ãã«ããã®ã€ã³ã¹ã¿ã³ã¹ãããã³ã³ã¬ã¯ã·ã§ã³ã®åœ¢åŒã§æ瀺ãããŸãã
APIå¿çã§éä¿¡ããããã«ãããã®ãšã³ãã£ãã£ã®JSONè¡šçŸãçæããããã»ã¹ã¯ã ã·ãªã¢ã©ã€ãŒãŒã·ã§ã³ãšåŒã°ããŸãã ããšãã°ã sequelize-to-json ïŒnpmïŒã acts_as_api ïŒrubygemïŒã jsonapi-rails ïŒrubygemïŒãªã©ãã·ãªã¢ã«åæ©èœãå®è¡ããããŸããŸãªãã¬ãŒã ã¯ãŒã¯çšã®ãã©ã°ã€ã³ãããã€ããããŸãã å®éããããã®ãã©ã°ã€ã³ã䜿çšãããšãç¹å®ã®ã¢ãã«ã§JSONãªããžã§ã¯ãã«å«ããå¿
èŠããããã£ãŒã«ãã®ãªã¹ããæå®ããããè¿œå ã®ã«ãŒã«ïŒååã®å€æŽãå€ã®åçãªèšç®ãªã©ïŒãæå®ãããã§ããŸãã
å°é£ã¯ãåãã¢ãã«ã®è€æ°ã®ç°ãªãJSONè¡šçŸãå¿
èŠãªå ŽåããŸãã¯ãªããžã§ã¯ãã«ãã¹ãããããšã³ãã£ãã£ïŒé¢é£ä»ãïŒãå«ãŸããå Žåã«å§ãŸããŸãã ã·ãªã¢ã©ã€ã¶ãŒã®ç¶æ¿ãåå©çšããªã³ã¯ãå¿
èŠã§ãã
ããŸããŸãªã¢ãžã¥ãŒã«ããããã®åé¡ãããŸããŸãªæ¹æ³ã§è§£æ±ºããŸãããä»æ§ãåã³åœ¹ç«ã€ãã©ããèããŠã¿ãŸãããã å®éãJSONè¡šçŸã®èŠä»¶ã«é¢ãããã¹ãŠã®æ
å ±ããã¹ãããããšã³ãã£ãã£ãå«ããã£ãŒã«ãã®å¯èœãªãã¹ãŠã®çµã¿åããã¯ããã§ã«ããã«ãããŸãã ãããã£ãŠãèªåã·ãªã¢ã©ã€ã¶ãŒãäœæã§ããŸãã
å°ããªã¢ãžã¥ãŒã«sequelize-serialize ïŒnpmïŒã«æ³šç®ããŠãã ãããããã«ãããSequelizeã¢ãã«ã§ãããè¡ãããšãã§ããŸãã ã¢ãã«ã€ã³ã¹ã¿ã³ã¹ãŸãã¯é
åãããã³å¿
èŠãªåè·¯ã®å
¥åãåãåãããã¹ãŠã®å¿
èŠãªãã£ãŒã«ããèæ
®ããé¢é£ãããšã³ãã£ãã£ã«ãã¹ããããåè·¯ã䜿çšããŠãã·ãªã¢ã«åããããªããžã§ã¯ããç¹°ãè¿ãæ§ç¯ããŸãã
ãã®ãããããã°ã®æçš¿ïŒã³ã¡ã³ããžã®ã³ã¡ã³ããå«ãïŒãæã£ãŠãããã¹ãŠã®ãŠãŒã¶ãŒãAPIããè¿ãå¿
èŠããããšããŸãã ããã«ã€ããŠã¯ã次ã®ä»æ§ã䜿çšããŠèª¬æããŸãã
ããã§ãSequelizeã䜿çšããŠã¯ãšãªãæ§ç¯ããäžèšã®ä»æ§ã«æ£ç¢ºã«äžèŽããã·ãªã¢ã«åããããªããžã§ã¯ããè¿ãããšãã§ããŸãã
import Router from 'koa-router'; import serialize from 'sequelize-serialize'; import { schemas } from './schemas'; const router = new Router(); router.get('/blog/users', async (ctx) => { const users = await User.findAll({ include: [{ association: User.posts, required: true, include: [Post.comments] }] }); ctx.body = serialize(users, schemas.UserWithPosts); });
ããã¯ã»ãšãã©éæ³ã§ãããïŒ
4.éçåä»ã
TypeScriptãŸãã¯Flowã䜿çšããŠããã»ã©ã¯ãŒã«ãªå Žåã¯ã ãç§ã®èŠªæãªãéçåã¯ã©ããªã®ïŒïŒã ã sw2dtsãŸãã¯swagger -to-flowtypeã¢ãžã¥ãŒã«ã䜿çšãããšãJSONã¹ããŒã ã«åºã¥ããŠå¿
èŠãªãã¹ãŠã®å®çŸ©ãçæãããã¹ããå
¥åããŒã¿ã ã·ãªã¢ã©ã€ã¶ãŒã®éçåä»ãã«äœ¿çšã§ããŸãã
tinyspec -j sw2dts ./swagger.json -o Api.d.ts --namespace Api
ããã§ãã³ã³ãããŒã©ãŒã§åã䜿çšã§ããŸãã
router.patch('/users/:id', async (ctx) => { // Specify type for request data object const userData: Api.UserUpdate = ctx.request.body.user; // Run spec validation await validate(schemas.UserUpdate, userData); // Query the database const user = await User.findById(ctx.params.id); await user.update(userData); // Return serialized result const serialized: Api.User = serialize(user, schemas.User); ctx.body = { user: serialized }; });
ãããŠãã¹ãã§ã¯ïŒ
it('Update user', async () => { // Static check for test input data. const updateData: Api.UserUpdate = { name: MODIFIED }; const res = await request.patch('/users/1', { user: updateData }); // Type helper for request response: const user: Api.User = res.body.user; expect(user).to.be.validWithSchema(schemas.User); expect(user).to.containSubset(updateData); });
çæãããã¿ã€ãå®çŸ©ã¯ãAPIãããžã§ã¯ãèªäœã ãã§ãªããã¯ã©ã€ã¢ã³ãã¢ããªã±ãŒã·ã§ã³ãããžã§ã¯ãã§ã䜿çšããŠãAPIãæ©èœããé¢æ°ã®ã¿ã€ããèšè¿°ããããšãã§ããããšã«æ³šæããŠãã ããã Angularã®é¡§å®¢éçºè
ã¯ããã®ã®ããã«ç¹ã«æºè¶³ããŠããŸãã
5.åãã£ã¹ãã¯ãšãªæåå
äœããã®çç±ã§APIãapplication/json
ã§ã¯ãªãMIMEã¿ã€ãapplication/x-www-form-urlencoded
ãªã¯ãšã¹ããåãå
¥ããå Žåããªã¯ãšã¹ãã®æ¬æã¯æ¬¡ã®ããã«ãªããŸãã
param1=value¶m2=777¶m3=false
åãããšãã¯ãšãªãã©ã¡ãŒã¿ã«ãé©çšãããŸãïŒããšãã°ãGETèŠæ±ã®å ŽåïŒã ãã®å ŽåãWebãµãŒããŒã¯ã¿ã€ããèªåçã«èªèã§ããŸããããã¹ãŠã®ããŒã¿ã¯æååã®åœ¢åŒã§ããããïŒ ããã§ã¯qpm npmã¢ãžã¥ãŒã«ãªããžããªã§èª¬æããŸãïŒã解æåŸã«æ¬¡ã®ãªããžã§ã¯ããååŸããŸãã
{ param1: 'value', param2: '777', param3: 'false' }
ãã®å ŽåãèŠæ±ã¯ã¹ããŒã ã«åŸã£ãŠæ€èšŒãããŸãããã€ãŸããåãã©ã¡ãŒã¿ãŒã®åœ¢åŒãæ£ããããšãæåã§ç¢ºèªããå¿
èŠãªã¿ã€ãã«ããå¿
èŠããããŸãã
ãæ³åã®ãšãããããã¯ä»æ§ãããã¹ãŠåãã¹ããŒã ã䜿çšããŠå®è¡ã§ããŸãã ãã®ãããªãšã³ããã€ã³ããšã¹ããŒã ããããšæ³åããŠãã ããïŒ
ãã®ãããªãšã³ããã€ã³ããžã®ãªã¯ãšã¹ãã®äŸã次ã«ç€ºããŸã
GET /posts?search=needle&offset=10&limit=1&filter[isRead]=true
ãã¹ãŠã®ãã©ã¡ãŒã¿ãŒãå¿
èŠãªåã«castQuery
é¢æ°ãäœæããŸãããã 次ã®ããã«ãªããŸãã
function castQuery(query, schema) { _.mapValues(query, (value, key) => { const { type } = schema.properties[key] || {}; if (!value || !type) { return value; } switch (type) { case 'integer': return parseInt(value, 10); case 'number': return parseFloat(value); case 'boolean': return value !== 'false'; default: return value; } }); }
ãã¹ããããã¹ããŒã ãé
åãããã³null
åããµããŒãããããå®å
šãªå®è£
ã¯ã cast-with-schema ïŒnpmïŒã§å©çšã§ããŸãã ããã§ãã³ãŒãã§äœ¿çšã§ããŸãã
router.get('/posts', async (ctx) => {
ãšã³ããã€ã³ãã³ãŒãã®4è¡ã®ãã¡ãä»æ§ã®3ã€ã®äœ¿çšã¹ããŒã ã«æ³šç®ããŠãã ããã
ãã¹ããã©ã¯ãã£ã¹
äœæãšå€æŽã®ããã®åå¥ã®ã¹ããŒã
éåžžããµãŒããŒã®å¿çãèšè¿°ããã¹ããŒã ã¯ãã¢ãã«ã®äœæããã³å€æŽã«äœ¿çšãããå
¥åãèšè¿°ããã¹ããŒã ãšã¯ç°ãªããŸãã ããšãã°ã POST
ããã³PATCH
èŠæ±ã§äœ¿çšå¯èœãªãã£ãŒã«ãã®ãªã¹ãã¯å³å¯ã«å¶éããå¿
èŠããããŸããã PATCH
èŠæ±ã§ã¯ãéåžžãã¹ããŒã ã®ãã¹ãŠã®ãã£ãŒã«ãããªãã·ã§ã³ã«ãªããŸãã çãã決å®ããã¹ããŒã ã¯ããèªç±ã§ãããããããŸããã
tinyspec CRUDLãšã³ããã€ã³ãã®èªåçæã§ã¯ã New
ããã³Update
ã®ãã¹ããã£ãã¯ã¹ã䜿çšããŸãã User*
ã¯æ¬¡ã®ããã«å®çŸ©ã§ããŸãã
User {id, email, name, isAdmin: b} UserNew !{email, name} UserUpdate !{email?, name?}
å€ãã¹ããŒã ã®åå©çšãŸãã¯ç¶æ¿ã«ããå¶çºçãªã»ãã¥ãªãã£åé¡ãåé¿ããããã«ãç°ãªãã¿ã€ãã®ã¢ã¯ã·ã§ã³ã«åãã¹ããŒã ã䜿çšããªãããã«ããŠãã ããã
ã¹ããŒãåã®ã»ãã³ãã£ã¯ã¹
åãã¢ãã«ã®å
容ã¯ããšã³ããã€ã³ãã«ãã£ãŠç°ãªãå ŽåããããŸãã ã¹ããŒãåã®With*
ããã³For*
æ¥å°ŸèŸã䜿çšããŠãããããã©ã®ããã«éããäœã®ããã«ãããã瀺ããŸãã tinyspecã¢ãã«ã§ã¯ãçžäºã«ç¶æ¿ããããšãã§ããŸãã äŸïŒ
User {name, surname} UserWithPhotos < User {photos: Photo[]} UserForAdmin < User {id, email, lastLoginAt: d}
æ¥å°ŸèŸã¯ããŸããŸãªçµã¿åãããå¯èœã§ãã äž»ãªãã®ã¯ããããã®ååãæ¬è³ªãåæ ããããã¥ã¡ã³ãã«ç²ŸéããŠããããšã§ãã
ã¯ã©ã€ã¢ã³ãã¿ã€ãã«ãããšã³ããã€ã³ãã®åé¢
å€ãã®å Žåãåããšã³ããã€ã³ãã¯ãã¯ã©ã€ã¢ã³ãã®ã¿ã€ããŸãã¯ãšã³ããã€ã³ãã«ã¢ã¯ã»ã¹ãããŠãŒã¶ãŒã®åœ¹å²ã«å¿ããŠç°ãªãããŒã¿ãè¿ããŸãã ããšãã°ã GET /users
ãšGET /messages
ã®ãšã³ããã€ã³ãã¯ãã¢ãã€ã«ã¢ããªã±ãŒã·ã§ã³ã®ãŠãŒã¶ãŒãšããã¯ãªãã£ã¹ãããŒãžã£ãŒã§å€§ããç°ãªãå ŽåããããŸãã åæã«ããšã³ããã€ã³ãèªäœã®ååãå€æŽããã®ã¯éåžžã«è€éã§ãã
åããšã³ããã€ã³ããè€æ°åèšè¿°ããããã«ããã¹ã®åŸã«è§ãã£ãã§ãã®ã¿ã€ããè¿œå ã§ããŸãã ã¿ã°ã䜿çšãããšäŸ¿å©ã§ããããã¯ããšã³ããã€ã³ãã®ããã¥ã¡ã³ããã°ã«ãŒãã«åå²ããã®ã«åœ¹ç«ã¡ãŸããåã°ã«ãŒãã¯ãAPIã®ã¯ã©ã€ã¢ã³ãã®ç¹å®ã®ã°ã«ãŒãåãã«èšèšãããŸãã äŸïŒ
Mobile app: GET /users (mobile) => UserForMobile[] CRM admin panel: GET /users (admin) => UserForAdmin[]
REST APIããã¥ã¡ã³ã
tinyspec圢åŒãŸãã¯OpenAPI圢åŒã®ä»æ§ãäœæããããHTMLã§çŸããããã¥ã¡ã³ããçæãããããAPIã䜿çšããéçºè
ã«å
¬éã§ããŸãã
åè¿°ã®ã¯ã©ãŠããµãŒãã¹ã«å ããŠãOpenAPI 2.0ãHTMLããã³PDFã«å€æããCLIããŒã«ãããããã®åŸãä»»æã®éçãã¹ãã£ã³ã°ã«ããŠã³ããŒãã§ããŸãã äŸïŒ
ãã£ãšäŸãç¥ã£ãŠããŸããïŒ ã³ã¡ã³ãã§å
±æããŠãã ããã
æ®å¿µãªããã1幎åã«ãªãªãŒã¹ãããOpenAPI 3.0ã¯ãŸã ååã«ãµããŒããããŠããããããã«åºã¥ããããã¥ã¡ã³ãã®é©åãªäŸã¯èŠã€ãããŸããã§ãããã¯ã©ãŠããœãªã¥ãŒã·ã§ã³ã§ãCLIããŒã«ã§ããããŸããã åãçç±ã§ãtinyspecã§ã¯OpenAPI 3.0ã¯ãŸã ãµããŒããããŠããŸããã
GitHubã«å
¬éãã
ããã¥ã¡ã³ããå
¬éããæãç°¡åãªæ¹æ³ã®1ã€ã¯GitHub Pagesã§ãã ãªããžããªèšå®ã§/docs
ãã£ã¬ã¯ããªã®éçããŒãžãµããŒããæå¹ã«ãããã®ãã©ã«ããŒã«HTMLããã¥ã¡ã³ããä¿åããã ãã§ãã

package.json
ã®scripts
ã§tinyspecãŸãã¯å¥ã®CLIããŒã«ã䜿çšããŠããã¥ã¡ã³ããçæããã³ãã³ããè¿œå ããã³ãããããšã«ããã¥ã¡ã³ããæŽæ°ã§ããŸãã
"scripts": { "docs": "tinyspec -h -o docs/", "precommit": "npm run docs" }
ç¶ç¶çã€ã³ãã°ã¬ãŒã·ã§ã³
ããã¥ã¡ã³ãã®çæãCIãµã€ã¯ã«ã«å«ããŠãããšãã°ãç°å¢ãAPIã®ããŒãžã§ã³ã«å¿ããŠç°ãªãã¢ãã¬ã¹ã§Amazon S3ã«çºè¡ã§ããŸãïŒäŸïŒ /docs/2.0
/docs/stable
ã /docs/staging
ã
Tinyspecã¯ã©ãŠã
tinyspecæ§æãæ°ã«å
¥ã£ãå Žåã¯ãtinyspec.cloudã§ã¢ãŒãªãŒã¢ããã¿ãŒãšããŠç»é²ã§ããŸãã ãã³ãã¬ãŒããå¹
åºãéžæããç¬èªã®ãã³ãã¬ãŒããéçºããæ©èœãåããããã¥ã¡ã³ããèªåçã«å
¬éããããã«ãã¯ã©ãŠããµãŒãã¹ãšCLIãåºç€ã«æ§ç¯ããŸãã
ãããã«
REST APIã®éçºã¯ãããããææ°ã®Webããã³ã¢ãã€ã«ãµãŒãã¹ã§äœæ¥ããããã»ã¹ã«ååšãããã¹ãŠã®äžã§æã楜ããã¢ã¯ãã£ããã£ã§ãã ãã©ãŠã¶ããªãã¬ãŒãã£ã³ã°ã·ã¹ãã ãç»é¢ãµã€ãºã®åç©åã¯ãããŸããããã¹ãŠããæå
ã§ã管çãããŠããŸãã
æäŸãããŠããããŸããŸãªèªååã®åœ¢ã§çŸåšã®ä»æ§ãšããŒãã¹ãç¶æããããšã§ããã®ããã»ã¹ã¯ããã«å¿«é©ã«ãªããŸãã ãã®ãããªAPIã¯ãæ§é åãããééçã§ä¿¡é Œæ§ãé«ããªããŸãã
å®éãç¥è©±ã®åµé ã«æºãã£ãŠãããšããŠãããªããããçŸããããŸãããïŒ