SlideShare a Scribd company logo
Building Quick APIs
by Gavin Pickin
https://github.com/gpickin/itb2021-building-quick-apis
Building Quick APIs
by Gavin Pickin
https://github.com/gpickin/itb2021-building-quick-apis
Gavin Pickin
Who am I?
• Software Consultant for Ortus Solutions
• Work with ColdBox, CommandBox, ContentBox
APIs and VueJS every day!
• Working with Coldfusion for 22 years
• Working with Javascript just as long
• Love learning and sharing the lessons learned
• From New Zealand, live in Bakersfield, Ca
• Loving wife, lots of kids, and countless critters
@gpickin
What is this talk?
We will setup a secure API using fluent query language - and
you’ll see how quick Quick development can be!
We will use
● ColdBox
● ColdBox’s built in REST BaseHandler
● Route Visualizer
● CBSecurity
● Quick ORM
What is this talk not?
● We will not be adding an API to a Legacy Site
● We will not be adding an API to a non ColdBox site
● It is not my CF Meetup Talk “Building APIs with
ColdFusion Part 1”
https://www.youtube.com/watch?v=UdgRt8HIKD0
BUT YOU COULD DO ALL OF THESE THINGS
WITH THE KNOWLEDGE FROM THIS TALK
Why should I give this talk?
• I build a lot of APIs for Customers
• I have seen many different ways to build an API
• I’m going to share some of the lessons I have learned.
• I try to present in step by step follow along later
format that I think you’ll love
• Hopefully you’ll learn something
• Hopefully it will be useful.
What is an API?
API is an application programming interface
More commonly when someone says API today they think of
an JSON API, usually we hope for a REST API but not always.
APIs can be xml, soap, but it’s very common to talk about
JSON apis, and that’s what we’re talking about today.
Although most of it is relatable.
REST JSON API vs JSON API
What is REST
REST is acronym for REpresentational State Transfer. It is
architectural style for distributed hypermedia systems and
was first presented by Roy Fielding in 2000 in his famous
dissertation.
All REST are APIs but not all APIs are REST.
Guiding Principles of REST
● Client–server
● Stateless
● Cacheable
● Uniform interface
● Layered system
The key abstraction of information in REST is a resource. Any information
that can be named can be a resource: a document or image, a temporal
service, a collection of other resources, a non-virtual object (e.g. a person),
and so on. REST uses a resource identifier to identify the particular resource
involved in an interaction between components.
What App are we working on?
SOAPBOX API - An API for a twitter clone we built for our ColdBox Zero to Hero Training.
Let’s do this!!!
Start up CommandBox!!!
Create a ColdBox App
box coldbox create app soapbox-api
This installs:
● ColdBox
● TestBox
● CommandBox-dotEnv
● CommandBox-cfconfig
● CommandBox-cfformat
Let’s update our .env
● Change the connection string to match our DB - soapboxapi
● Change the DB_DATABASE to match our DB - soapboxapi
● Change ports, user name and password to match
Let’s start our server
That port matches the testbox runner in the box.json
http://127.0.0.1:53163/ - our site
http://127.0.0.1:53163/tests/runner.cfm - our tests
box start port=53163
Let’s plan our API
What is an Example URL - /api/v01/users
We’re going to make API a module with a V01 module inside of it.
● Add modules_app folder for this apps custom modules
● Add modules_app/api folder with ModuleConfig.cfc
● Add modules_app/api/modules/api-v01 folder with ModuleConfig.cfc
API - V01
Let’s build out our API V01 Module
● Add modules_app/api/modules/api-v01/config Folder with Router.cfc
● Setup the base route of “/” to go to main.index
● Add api-v01/handlers/main.cfc which extends coldbox.system.RestHandler
● Add index event in the main handler
● Index event should sets data in the response object to welcome the user to
the api.
http://127.0.0.1:53163/api/v01
API - V01
Let’s plan our API
USER resource
GET /api/v02/users - get a list of users
POST /api/v02/users - create a new user
GET /api/v02/users/:userID - get a single user
POST /api/v02/users/:userID - update a single user
DELETE /api/v02/users/:userID - delete a single user
API - V02
Add User Resource to Router.cfc
resources(
resource = "users",
handler = "users",
parameterName = "userID",
only = [ "index", "show", "create", "update", "delete" ]
);
API - V02
Let’s view our Routes
box install route-visualizer
API - V02
Build our Users Handler
function index( event, rc, prc ){
event.getResponse().setData( "List Users" );
}
function create( event, rc, prc ){
event.getResponse().setData( "Create User" );
}
API - V02
http://127.0.0.1:53163/api/v02/users
Secure our CUD Actions
Let’s use CBSecurity to lock down our non READ functionality
● CREATE
● UPDATE
● DELETE
API - V03
Install CBSecurity
Add the Config to your config/ColdBox.cfc
Add a JWT Secret incase .env has a blank one
Add “api-v03:main.index” for “invalidAuthenticationEvent”
Add “api-v03:main.index” for “invalidAuthorizationEvent”
API - V03
box install cbsecurity
Add Secured Metadata
function create( event, rc, prc ) secured{
event.getResponse().setData( "Create Users" );
}
API - V03
Check our Security
http://127.0.0.1:53163/api/v03/users?_method=post
You will get redirected to main.index
API - V03
Updating Redirects for API Authentication
and Authorization Issues
You should really setup your main ColdBox app for an HTML page and
your API module for an API page
Add settings to the API-v04 Module Config
Add “api-v04:main.index” for “invalidAuthenticationEvent”
Add “api-v04:main.index” for “invalidAuthorizationEvent”
API - V04
Updating Redirects for API Authentication
and Authorization Issues
http://127.0.0.1:53163/api/v04/users/?_method=post
Now we are redirected to an API Handler
You can add new methods for onInvalidAuth and or
onInvalidAuthorization to be more specific in your responses
API - V04
Install and Configure Quick
add a datasource definition in Application.cfc
API - V05
this.datasource = "soapboxapi";
box install quick
Create our Quick User Entity
component extends="quick.models.BaseEntity" accessors="true" {
property name="id" type="string";
property name="username" type="string";
property name="email" type="string";
property name="password" type="string";
}
API - V05
Quick is Smart
● Quick guesses the table is named Users because the entity is User
● Quick guesses the Primary Key is ID
● Quick uses the Datasource defined in the Application.cfc
● Quick can make a Virtual Service for us from a WireBox Injection
property name="userService" inject="quickService:User@api-v05";
Quick does a lot for us
API - V05
Show a User
function show( event, rc, prc ){
event.paramValue( "userID", 0 );
event.getResponse().setData(
userService
.findOrFail( rc.userID )
.getMemento()
);
}
API - V05
Show a User
http://127.0.0.1:53163/api/v05/users/2
API - V05
What if we can’t find that User?
http://127.0.0.1:53163/api/v05/users/x
404 response
API - V05
Return a list of Users
function index( event, rc, prc ){
event.getResponse().setData(
userService
.asMemento()
.get()
);
}
API - V06
What is asMemento()
asMemento() is a special function that tells quick you want to map over
all of the objects in the array, and call getMemento() on all of the items
.get().getMemento() doesn’t work because .get() returns an array
As memento is essentially the following done for you.
...get().map( (item) => { return item.getMemento() } );
API - V06
Reinit and check the API
http://127.0.0.1:53163/api/v06/users/
API - V06
Configuring Mementifier
● We don’t want to return all of the fields
● For example Password, even if it is encrypted
https://github.com/coldbox-modules/mementifier
API - V07
Mementifier Inline Excludes
API - V07
userService
.asMemento(
excludes=[ "password" ]
)
.get()
http://127.0.0.1:53163/api/v07/users
Mementifier - Inline Includes
API - V08
userService
.asMemento(
includes=[ "id", "username", "email" ],
ignoreDefaults = true
)
.get()
http://127.0.0.1:53163/api/v08/users
Mementifier - Entity Config
API - V09
//models/User.cfc
this.memento = {
defaultIncludes=[ "username", "email" ],
neverInclude=["password"]
}
http://127.0.0.1:53163/api/v09/users/2
Mementifier - Inline Includes
API - V10
function index( event, rc, prc ){
event.getResponse().setData(
userService
.asMemento(
includes=[ “ID” ]
)
.get()
);
}
http://127.0.0.1:53163/api/v10/users/
Using Where to Filter Users
API - V11
event.paramValue( "username_filter", "" );
event.getResponse().setData(
userService
.where( "username", "like", '%#rc.username_filter#%')
.asMemento(
includes="ID"
)
.get()
);
http://127.0.0.1:53163/api/v11/users?username_filter=gp
Using Scope to Filter Users
API - V12
//handler - use this
userService.whereUsernameLike(rc.username_filter)
// not this
userService.where( "username", "like", '%#rc.username_filter#%')
// models/User.cfc
function scopeWhereUsernameLike( qb, filter ){
qb.where( "Username", "like", '%#filter#%' )
}
http://127.0.0.1:53163/api/v12/users?username_filter=gp
Using Dynamic Where to Match Users
API - V13
userService
.whereUsernameLike(rc.username_filter)
.whereUsername(rc.username)
.asMemento(
includes="ID"
)
.get()
http://127.0.0.1:53163/api/v13/users?username_filter=gp
Using When for Empty Variables
API - V14
userService
.whereUsernameLike(rc.username_filter)
.when( len( rc.username ), function( q ) {
q.whereUsername(rc.username)
}
.asMemento(
includes="ID"
)
.get()
http://127.0.0.1:53163/api/v14/users?username_filter=gp
Using When for Empty Variables
API - V14
userService
.whereUsernameLike(rc.username_filter)
.when( len( rc.username ), function( q ) {
q.whereUsername(rc.username)
}
.asMemento(
includes="ID"
)
.get()
http://127.0.0.1:53163/api/v14/users?username=gp
Using When for Empty Variables
API - V14
userService
.whereUsernameLike(rc.username_filter)
.when( len( rc.username ), function( q ) {
q.whereUsername(rc.username)
}
.asMemento(
includes="ID"
)
.get()
http://127.0.0.1:53163/api/v14/users?username=gpickin
Using ‘Or’ for Similar Filters
API - V15
userService
.whereUsernameLike(rc.username_filter)
.when( len( rc.username ), function( q ) {
q.whereUsername(rc.username)
}
.asMemento(
includes="ID"
)
.get()
http://127.0.0.1:53163/api/v14/users?username=gpickin&username_filter=luis
Using ‘Or’ for Similar Filters
API - V15
userService
.whereUsernameLike(rc.username_filter)
.orWhere( function(q){
q.when( len( rc.username ), function( q2 ) {
q2.orwhereUsername(rc.username)
})
})
http://127.0.0.1:53163/api/v14/users?username=gpickin&username_filter=lui
Setup Rant Resource
API - V16
resources(
resource = "rants",
handler = "rants",
parameterName = "rantID",
only = [ "index", "show", "create", "update", "delete" ]
);
Setup Rant Handler
API - V16
/**
* Manage Rants API Handler
*
*/
component extends="coldbox.system.RestHandler "{
/**
* Display a list of Rants
*/
function index( event, rc, prc ){
event.getResponse().setData( "Show Rants");
}
/**
* Create a Rant
*/
Test Rant Resource
API - V16
http://127.0.0.1:53163/api/v16/rants? http://127.0.0.1:53163/api/v16/rants/6
Create Rant Entity
API - V17
component extends="quick.models.BaseEntity" accessors="true" {
property name="id" type="string";
property name="body" type="string";
property name="createdDate" type="date";
property name="modifiedDate" type="date";
property name="userID" type="string";
}
Inject Rant Service and Return Rants
API - V17
property name="rantService" inject="quickService:Rant@api-v17";
function index( event, rc, prc ){
event.getResponse().setData(
rantService
.asMemento()
.get()
);
}
http://127.0.0.1:53163/api/v17/rants
Paginate the Rants Returned
API - V18
http://127.0.0.1:53163/api/v17/rants
● We don’t want to return every record, it will only
grow longer and longer over time, and
performance will suffer
● ColdBox RESTBaseHandler already has Pagination
information
● Let’s use Quick’s Paginate function
Paginate the Rants Returned
API - V18
http://127.0.0.1:53163/api/v18/rants
event.getResponse().setData(
rantService
.asMemento()
.paginate(1,10)
);
Paginate the Rants Returned
API - V18
http://127.0.0.1:53163/api/v18/rants
event.getResponse().setData(
rantService
.asMemento()
.paginate(1,10)
);
Why do we have 2 sets of Pagination Data???
Use SetDataWithPagination
API - V19
http://127.0.0.1:53163/api/v19/rants
event
.getResponse()
.setDataWithPagination(
rantService
.asMemento()
.paginate(1,10)
);
Return Rants User Information
// Add User Relationship to Rant.cfc
function user(){
return hasOne( "User@api-v20", "id", "userID" );
}
API - V20
// Update Rants.cfc handler
event.getResponse().setDataWithPagination(
rantService
.asMemento( includes="user" )
.paginate(1,10)
);
Return Rants User Information
API - V20
http://127.0.0.1:53163/api/v20/rants
Return Rants User Information
API - V20
● Nested Fields
● What if we just want 1 field?
● How is Mementifier getting the
data??
http://127.0.0.1:53163/api/v20/rants
Return Rants with SubSelect User Information
API - V21
event.getResponse().setDataWithPagination(
rantService
.addSubselect( "username", "user.username")
.asMemento()
.paginate(1,10)
);
http://127.0.0.1:53163/api/v21/rants
Return a Count of Rants for Users
API - V22
// Add Rants Relationship to models/User.cfc
function rants(){
return hasMany( "Rant@api-v22", "userID", "id" );
}
// Update users.cfc handler
userService
.withCount( "rants" )
.asMemento(
includes=["ID","rantsCount"]
)
.get()
http://127.0.0.1:53163/api/v22/users
Return Recent Rants for Users
API - V23
//Router.cfc
get( "/users/:userID/rants"
, "userRants.index" );
// Handlers/userRants.cfc
function index( event, rc, prc ){
event.paramValue( "userID", "" );
event.paramValue( "page", 1 );
event.paramValue( "per_page", 10 );
event.getResponse().setDataWithPagination
(
rantService
.where( "userID", rc.userID )
.orderBy( "createdDate", "desc" )
.asMemento()
.paginate( rc.page, rc.per_page )
);
}
http://127.0.0.1:53163/api/v23/users/2/rants
Create a new Rant
API - V24
http://127.0.0.1:53163/api/v24/rants?_method=post&userID=2&body=Adding%20a%20Rant
function create( event, rc, prc ){
event.paramValue( "userID", "" );
event.paramValue( "body", "" );
userService
.findOrFail( rc.userID )
.rants()
.create( { body: rc.body } );
event.getResponse().setData( "Rant Created" );
}
View the new Rant
API - V24
http://127.0.0.1:53163/api/v24/users/2/rants
What if we add an Empty Rant
API - V24
http://127.0.0.1:53163/api/v24/rants?_method=post&userID=2&body=
Not User
Friendly
Validate a new Rant
API - V25
box Install cbvalidation
Validate a new Rant - Handler Constraints
API - V25
var validationResult = validate(
target = rc,
constraints = { body : { required : true } }
)
if ( !validationResult.hasErrors() ) {
// Normal API Response
} else {
event.getResponse()
.setError( true )
.addMessage( validationResult.getAllErrors() )
.setStatusCode( 400 )
.setStatusText( "Validation error" );
}
http://127.0.0.1:53163/api/v25/rants?_method=post&userID=2&body=
Validate a new Rant - Entity Constraints
API - V26
//Rant.cfc
this.constraints = {
body : { required : true },
userID: { required : true, type : "numeric" }
};
// Handler/rants.cfc
var rant = getInstance( "Rant@api-v26" )
.fill({ body: rc.body, userID: rc.userID });
validateOrFail( rant );
var user = userService.findOrFail( rc.userID );
rant.save();
event.getResponse().setData( "Rant Created" );
Validate a new Rant - Entity Constraints
API - V26
http://127.0.0.1:53163/api/v26/rants?_method=post&userID=2&body=
Body is missing or empty
Validate a new Rant - Entity Constraints
API - V26
http://127.0.0.1:53163/api/v26/rants?_method=post&userID=xxx&body=MyRant
UserID is not numeric
Validate a new Rant - Entity Constraints
API - V26
http://127.0.0.1:53163/api/v26/rants?_method=post&userID=0&body=MyRant
UserID is numeric but not in User table
Edit a Rant
API - V27
event.paramValue( "rantID", 0 );
event.paramValue( "body", "" );
var rant = rantService.findOrFail( rc.rantID )
.fill({ body: rc.body });
validateOrFail( rant );
rant.save();
event.getResponse().setData( "Rant Updated" );
Edit a Rant - Success
API - V27
http://127.0.0.1:53163/api/v27/rants/178/?_method=put&body=Yes%20thats%20is%20right
Edit a Rant - Missing Body
API - V27
http://127.0.0.1:53163/api/v27/rants/178/?_method=put&body=
Delete a Rant
API - V28
function delete( event, rc, prc ){
event.paramValue( "rantID", 0 );
var rant = rantService.findOrFail( rc.rantID ).delete();
event.getResponse().setData( "Delete a Rant" );
}
Delete a Rant that doesn’t Exist
API - V28
http://127.0.0.1:53163/api/v28/rants/70000/?_method=delete
Delete a Rant that does Exist
API - V28
http://127.0.0.1:53163/api/v28/rants/7/?_method=delete
Secure the Add / Edit Rant
function create( event, rc, prc ) secured{ … }
function update( event, rc, prc ) secured{ … }
API - V29
NOT LOGGED IN
http://127.0.0.1:53163/api/v29/rants/178/?_method=put&body=hiya
Secure the Delete Rant with a Special Permission
API - V29
function delete( event, rc, prc ) secured="RANT__DELETE"{ … }
http://127.0.0.1:53163/api/v29/rants/7/?_method=delete
NOT LOGGED IN
Create a Generate JWT Endpoint
//Router.cfc
post( "/login", "session.create" );
delete( "/logout", "session.delete" );
//handlers/session.cfc
function create( event, rc, prc ){
token = jwtAuth().fromuser( userService.findOrFail( 2 ) );
event.getResponse()
.addMessage( "Token Created" )
.setData( token );
}
function delete( event, rc, prc ){
jwtAuth().logout();
event.getResponse().setData( "Delete Session - Log Out" );
}
API - V30
Decorate User.cfc Entity for JWT
API - V30
/**
* A struct of custom claims to add to the JWT token
*/
struct function getJWTCustomClaims(){
return {};
}
/**
* This function returns an array of all the scopes that should be attached to the JWT
* token that will be used for authorization.
*/
array function getJWTScopes(){
return [];
}
Hit Generate JWT Endpoint
API - V30
http://127.0.0.1:53163/api/v30/login?_method=post
Use JWT to Add a Rant
API - V30
But Wait, there’s more (I wish)
I really wanted to show you more, but we don’t have all day.
Join me at the CF Online Meetup where I do this again, maybe
slower/better
Join me for the October Ortus Webinar as we build on top of this.
We might make a CFCasts series out of this if you like this style of
content
You can use images and add them opacity at 90%. This will blend the image
with the gradient background. This is a good option for subtitles also.
Testing APIs with TestBox
with Javier Quintero
Up Next
E-mail: gavin@ortussolutions.com
Twitter: @gpickin
Gavin Pickin
Senior Software Consultant
Ortus Solutions, Corp
https://github.com/gpickin/itb2021-building-quick-apis

More Related Content

What's hot

OAuth 2.0
OAuth 2.0OAuth 2.0
OAuth 2.0
Uwe Friedrichsen
 
Implementing OAuth
Implementing OAuthImplementing OAuth
Implementing OAuth
leahculver
 
いまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authlete
いまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authleteいまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authlete
いまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authlete
Tatsuo Kudo
 
OAuth2 - Introduction
OAuth2 - IntroductionOAuth2 - Introduction
OAuth2 - Introduction
Knoldus Inc.
 
Secure your app with keycloak
Secure your app with keycloakSecure your app with keycloak
Secure your app with keycloak
Guy Marom
 
OAuth 2.0 and OpenId Connect
OAuth 2.0 and OpenId ConnectOAuth 2.0 and OpenId Connect
OAuth 2.0 and OpenId Connect
Saran Doraiswamy
 
認証の課題とID連携の実装 〜ハンズオン〜
認証の課題とID連携の実装 〜ハンズオン〜認証の課題とID連携の実装 〜ハンズオン〜
認証の課題とID連携の実装 〜ハンズオン〜
Masaru Kurahayashi
 
OAuth & OpenID Connect Deep Dive
OAuth & OpenID Connect Deep DiveOAuth & OpenID Connect Deep Dive
OAuth & OpenID Connect Deep Dive
Nordic APIs
 
FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...
FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...
FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...
Tatsuo Kudo
 
Identity Assurance with OpenID Connect
Identity Assurance with OpenID ConnectIdentity Assurance with OpenID Connect
Identity Assurance with OpenID Connect
Torsten Lodderstedt
 
IdP, SAML, OAuth
IdP, SAML, OAuthIdP, SAML, OAuth
IdP, SAML, OAuth
Dan Brinkmann
 
OAuth in the Wild
OAuth in the WildOAuth in the Wild
OAuth in the Wild
Victor Rentea
 
Security enforcement of Java Microservices with Apiman & Keycloak
Security enforcement of Java Microservices with Apiman & KeycloakSecurity enforcement of Java Microservices with Apiman & Keycloak
Security enforcement of Java Microservices with Apiman & Keycloak
Charles Moulliard
 
Webauthn Tutorial
Webauthn TutorialWebauthn Tutorial
Webauthn Tutorial
FIDO Alliance
 
Spring security
Spring securitySpring security
Spring security
Saurabh Sharma
 
Demystifying OAuth 2.0
Demystifying OAuth 2.0Demystifying OAuth 2.0
Demystifying OAuth 2.0
Karl McGuinness
 
Keycloak Single Sign-On
Keycloak Single Sign-OnKeycloak Single Sign-On
Keycloak Single Sign-On
Ravi Yasas
 
Pki (Public Key Infrastructure) 에 대한 쉬운 설명
Pki (Public Key Infrastructure) 에 대한 쉬운 설명Pki (Public Key Infrastructure) 에 대한 쉬운 설명
Pki (Public Key Infrastructure) 에 대한 쉬운 설명
Crazia Wolfgang
 
JSON Web Token
JSON Web TokenJSON Web Token
JSON Web Token
Deddy Setyadi
 
Ibm mq with c# sending and receiving messages
Ibm mq with c# sending and receiving messagesIbm mq with c# sending and receiving messages
Ibm mq with c# sending and receiving messages
Shreesha Rao
 

What's hot (20)

OAuth 2.0
OAuth 2.0OAuth 2.0
OAuth 2.0
 
Implementing OAuth
Implementing OAuthImplementing OAuth
Implementing OAuth
 
いまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authlete
いまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authleteいまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authlete
いまどきの OAuth / OpenID Connect (OIDC) 一挙おさらい (2020 年 2 月) #authlete
 
OAuth2 - Introduction
OAuth2 - IntroductionOAuth2 - Introduction
OAuth2 - Introduction
 
Secure your app with keycloak
Secure your app with keycloakSecure your app with keycloak
Secure your app with keycloak
 
OAuth 2.0 and OpenId Connect
OAuth 2.0 and OpenId ConnectOAuth 2.0 and OpenId Connect
OAuth 2.0 and OpenId Connect
 
認証の課題とID連携の実装 〜ハンズオン〜
認証の課題とID連携の実装 〜ハンズオン〜認証の課題とID連携の実装 〜ハンズオン〜
認証の課題とID連携の実装 〜ハンズオン〜
 
OAuth & OpenID Connect Deep Dive
OAuth & OpenID Connect Deep DiveOAuth & OpenID Connect Deep Dive
OAuth & OpenID Connect Deep Dive
 
FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...
FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...
FAPI (Financial-grade API) and CIBA (Client Initiated Backchannel Authenticat...
 
Identity Assurance with OpenID Connect
Identity Assurance with OpenID ConnectIdentity Assurance with OpenID Connect
Identity Assurance with OpenID Connect
 
IdP, SAML, OAuth
IdP, SAML, OAuthIdP, SAML, OAuth
IdP, SAML, OAuth
 
OAuth in the Wild
OAuth in the WildOAuth in the Wild
OAuth in the Wild
 
Security enforcement of Java Microservices with Apiman & Keycloak
Security enforcement of Java Microservices with Apiman & KeycloakSecurity enforcement of Java Microservices with Apiman & Keycloak
Security enforcement of Java Microservices with Apiman & Keycloak
 
Webauthn Tutorial
Webauthn TutorialWebauthn Tutorial
Webauthn Tutorial
 
Spring security
Spring securitySpring security
Spring security
 
Demystifying OAuth 2.0
Demystifying OAuth 2.0Demystifying OAuth 2.0
Demystifying OAuth 2.0
 
Keycloak Single Sign-On
Keycloak Single Sign-OnKeycloak Single Sign-On
Keycloak Single Sign-On
 
Pki (Public Key Infrastructure) 에 대한 쉬운 설명
Pki (Public Key Infrastructure) 에 대한 쉬운 설명Pki (Public Key Infrastructure) 에 대한 쉬운 설명
Pki (Public Key Infrastructure) 에 대한 쉬운 설명
 
JSON Web Token
JSON Web TokenJSON Web Token
JSON Web Token
 
Ibm mq with c# sending and receiving messages
Ibm mq with c# sending and receiving messagesIbm mq with c# sending and receiving messages
Ibm mq with c# sending and receiving messages
 

Similar to Itb 2021 - Bulding Quick APIs by Gavin Pickin

Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...
Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...
Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...
Amazon Web Services
 
Into The Box | Alexa and ColdBox Api's
Into The Box | Alexa and ColdBox Api'sInto The Box | Alexa and ColdBox Api's
Into The Box | Alexa and ColdBox Api's
Ortus Solutions, Corp
 
Crafting APIs
Crafting APIsCrafting APIs
Crafting APIs
Tatiana Al-Chueyr
 
API Workshop: Deep dive into REST APIs
API Workshop: Deep dive into REST APIsAPI Workshop: Deep dive into REST APIs
API Workshop: Deep dive into REST APIs
Tom Johnson
 
Azure APIM Presentation to understand about.pptx
Azure APIM Presentation to understand about.pptxAzure APIM Presentation to understand about.pptx
Azure APIM Presentation to understand about.pptx
pythagorus143
 
Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs
 Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs
Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs
AWS Chicago
 
Introduction to google endpoints
Introduction to google endpointsIntroduction to google endpoints
Introduction to google endpoints
Shinto Anto
 
Releasing Software Quickly and Reliably with AWS CodePipline
Releasing Software Quickly and Reliably with AWS CodePiplineReleasing Software Quickly and Reliably with AWS CodePipline
Releasing Software Quickly and Reliably with AWS CodePipline
Amazon Web Services
 
Managing the Continuous Delivery of Code to AWS Lambda
Managing the Continuous Delivery of Code to AWS LambdaManaging the Continuous Delivery of Code to AWS Lambda
Managing the Continuous Delivery of Code to AWS Lambda
Amazon Web Services
 
Devoxx 2018 - Pivotal and AxonIQ - Quickstart your event driven architecture
Devoxx 2018 -  Pivotal and AxonIQ - Quickstart your event driven architectureDevoxx 2018 -  Pivotal and AxonIQ - Quickstart your event driven architecture
Devoxx 2018 - Pivotal and AxonIQ - Quickstart your event driven architecture
Ben Wilcock
 
Spring Cloud Function & Project riff #jsug
Spring Cloud Function & Project riff #jsugSpring Cloud Function & Project riff #jsug
Spring Cloud Function & Project riff #jsug
Toshiaki Maki
 
MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...
MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...
MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...
MongoDB
 
"Service Worker: Let Your Web App Feel Like a Native "
"Service Worker: Let Your Web App Feel Like a Native ""Service Worker: Let Your Web App Feel Like a Native "
"Service Worker: Let Your Web App Feel Like a Native "
FDConf
 
Continuous Delivery with AWS Lambda - AWS April 2016 Webinar Series
Continuous Delivery with AWS Lambda - AWS April 2016 Webinar SeriesContinuous Delivery with AWS Lambda - AWS April 2016 Webinar Series
Continuous Delivery with AWS Lambda - AWS April 2016 Webinar Series
Amazon Web Services
 
Application Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless WorldApplication Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless World
Amazon Web Services
 
SharePoint Fest Seattle - SharePoint Framework, Angular & Azure Functions
SharePoint Fest Seattle - SharePoint Framework, Angular & Azure FunctionsSharePoint Fest Seattle - SharePoint Framework, Angular & Azure Functions
SharePoint Fest Seattle - SharePoint Framework, Angular & Azure Functions
Sébastien Levert
 
2014 SharePoint Saturday Melbourne Apps or not to Apps
2014 SharePoint Saturday Melbourne Apps or not to Apps2014 SharePoint Saturday Melbourne Apps or not to Apps
2014 SharePoint Saturday Melbourne Apps or not to Apps
Gilles Pommier
 
Migrate your Existing Express Apps to AWS Lambda and Amazon API Gateway
Migrate your Existing Express Apps to AWS Lambda and Amazon API GatewayMigrate your Existing Express Apps to AWS Lambda and Amazon API Gateway
Migrate your Existing Express Apps to AWS Lambda and Amazon API Gateway
Amazon Web Services
 
I Love APIs - Oct 2015
I Love APIs - Oct 2015I Love APIs - Oct 2015
I Love APIs - Oct 2015
Mike McNeil
 
Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...
Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...
Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...
Amazon Web Services
 

Similar to Itb 2021 - Bulding Quick APIs by Gavin Pickin (20)

Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...
Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...
Releasing Software Quickly and Reliably With AWS CodePipeline by Mark Mansour...
 
Into The Box | Alexa and ColdBox Api's
Into The Box | Alexa and ColdBox Api'sInto The Box | Alexa and ColdBox Api's
Into The Box | Alexa and ColdBox Api's
 
Crafting APIs
Crafting APIsCrafting APIs
Crafting APIs
 
API Workshop: Deep dive into REST APIs
API Workshop: Deep dive into REST APIsAPI Workshop: Deep dive into REST APIs
API Workshop: Deep dive into REST APIs
 
Azure APIM Presentation to understand about.pptx
Azure APIM Presentation to understand about.pptxAzure APIM Presentation to understand about.pptx
Azure APIM Presentation to understand about.pptx
 
Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs
 Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs
Serverless Framework Workshop - Tyler Hendrickson, Chicago/burbs
 
Introduction to google endpoints
Introduction to google endpointsIntroduction to google endpoints
Introduction to google endpoints
 
Releasing Software Quickly and Reliably with AWS CodePipline
Releasing Software Quickly and Reliably with AWS CodePiplineReleasing Software Quickly and Reliably with AWS CodePipline
Releasing Software Quickly and Reliably with AWS CodePipline
 
Managing the Continuous Delivery of Code to AWS Lambda
Managing the Continuous Delivery of Code to AWS LambdaManaging the Continuous Delivery of Code to AWS Lambda
Managing the Continuous Delivery of Code to AWS Lambda
 
Devoxx 2018 - Pivotal and AxonIQ - Quickstart your event driven architecture
Devoxx 2018 -  Pivotal and AxonIQ - Quickstart your event driven architectureDevoxx 2018 -  Pivotal and AxonIQ - Quickstart your event driven architecture
Devoxx 2018 - Pivotal and AxonIQ - Quickstart your event driven architecture
 
Spring Cloud Function & Project riff #jsug
Spring Cloud Function & Project riff #jsugSpring Cloud Function & Project riff #jsug
Spring Cloud Function & Project riff #jsug
 
MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...
MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...
MongoDB World 2018: Tutorial - Got Dibs? Building a Real-Time Bidding App wit...
 
"Service Worker: Let Your Web App Feel Like a Native "
"Service Worker: Let Your Web App Feel Like a Native ""Service Worker: Let Your Web App Feel Like a Native "
"Service Worker: Let Your Web App Feel Like a Native "
 
Continuous Delivery with AWS Lambda - AWS April 2016 Webinar Series
Continuous Delivery with AWS Lambda - AWS April 2016 Webinar SeriesContinuous Delivery with AWS Lambda - AWS April 2016 Webinar Series
Continuous Delivery with AWS Lambda - AWS April 2016 Webinar Series
 
Application Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless WorldApplication Lifecycle Management in a Serverless World
Application Lifecycle Management in a Serverless World
 
SharePoint Fest Seattle - SharePoint Framework, Angular & Azure Functions
SharePoint Fest Seattle - SharePoint Framework, Angular & Azure FunctionsSharePoint Fest Seattle - SharePoint Framework, Angular & Azure Functions
SharePoint Fest Seattle - SharePoint Framework, Angular & Azure Functions
 
2014 SharePoint Saturday Melbourne Apps or not to Apps
2014 SharePoint Saturday Melbourne Apps or not to Apps2014 SharePoint Saturday Melbourne Apps or not to Apps
2014 SharePoint Saturday Melbourne Apps or not to Apps
 
Migrate your Existing Express Apps to AWS Lambda and Amazon API Gateway
Migrate your Existing Express Apps to AWS Lambda and Amazon API GatewayMigrate your Existing Express Apps to AWS Lambda and Amazon API Gateway
Migrate your Existing Express Apps to AWS Lambda and Amazon API Gateway
 
I Love APIs - Oct 2015
I Love APIs - Oct 2015I Love APIs - Oct 2015
I Love APIs - Oct 2015
 
Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...
Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...
Integrating Infrastructure as Code into a Continuous Delivery Pipeline | AWS ...
 

More from Gavin Pickin

Containerizing ContentBox CMS
Containerizing ContentBox CMSContainerizing ContentBox CMS
Containerizing ContentBox CMS
Gavin Pickin
 
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
Gavin Pickin
 
AN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLE
AN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLEAN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLE
AN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLE
Gavin Pickin
 
3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API
Gavin Pickin
 
Take home your very own free Vagrant CFML Dev Environment - Presented at dev....
Take home your very own free Vagrant CFML Dev Environment - Presented at dev....Take home your very own free Vagrant CFML Dev Environment - Presented at dev....
Take home your very own free Vagrant CFML Dev Environment - Presented at dev....
Gavin Pickin
 
How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016
How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016
How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016
Gavin Pickin
 
BDD Testing and Automating from the trenches - Presented at Into The Box June...
BDD Testing and Automating from the trenches - Presented at Into The Box June...BDD Testing and Automating from the trenches - Presented at Into The Box June...
BDD Testing and Automating from the trenches - Presented at Into The Box June...
Gavin Pickin
 
How do I write Testable Javascript so I can Test my CF API on Server and Client
How do I write Testable Javascript so I can Test my CF API on Server and ClientHow do I write Testable Javascript so I can Test my CF API on Server and Client
How do I write Testable Javascript so I can Test my CF API on Server and Client
Gavin Pickin
 
Just Mock It - Mocks and Stubs
Just Mock It - Mocks and StubsJust Mock It - Mocks and Stubs
Just Mock It - Mocks and Stubs
Gavin Pickin
 
How do I write Testable Javascript?
How do I write Testable Javascript?How do I write Testable Javascript?
How do I write Testable Javascript?
Gavin Pickin
 
Getting your Hooks into Cordova
Getting your Hooks into CordovaGetting your Hooks into Cordova
Getting your Hooks into Cordova
Gavin Pickin
 
Setting up your Multi Engine Environment - Apache Railo and ColdFusion
Setting up your Multi Engine Environment - Apache Railo and ColdFusionSetting up your Multi Engine Environment - Apache Railo and ColdFusion
Setting up your Multi Engine Environment - Apache Railo and ColdFusion
Gavin Pickin
 

More from Gavin Pickin (12)

Containerizing ContentBox CMS
Containerizing ContentBox CMSContainerizing ContentBox CMS
Containerizing ContentBox CMS
 
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
ColdBox APIs + VueJS - powering Mobile, Desktop and Web Apps with 1 VueJS cod...
 
AN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLE
AN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLEAN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLE
AN EXERCISE IN CLEANER CODE - FROM LEGACY TO MAINTAINABLE
 
3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API3 WAYS TO TEST YOUR COLDFUSION API
3 WAYS TO TEST YOUR COLDFUSION API
 
Take home your very own free Vagrant CFML Dev Environment - Presented at dev....
Take home your very own free Vagrant CFML Dev Environment - Presented at dev....Take home your very own free Vagrant CFML Dev Environment - Presented at dev....
Take home your very own free Vagrant CFML Dev Environment - Presented at dev....
 
How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016
How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016
How do I write Testable Javascript - Presented at dev.Objective() June 16, 2016
 
BDD Testing and Automating from the trenches - Presented at Into The Box June...
BDD Testing and Automating from the trenches - Presented at Into The Box June...BDD Testing and Automating from the trenches - Presented at Into The Box June...
BDD Testing and Automating from the trenches - Presented at Into The Box June...
 
How do I write Testable Javascript so I can Test my CF API on Server and Client
How do I write Testable Javascript so I can Test my CF API on Server and ClientHow do I write Testable Javascript so I can Test my CF API on Server and Client
How do I write Testable Javascript so I can Test my CF API on Server and Client
 
Just Mock It - Mocks and Stubs
Just Mock It - Mocks and StubsJust Mock It - Mocks and Stubs
Just Mock It - Mocks and Stubs
 
How do I write Testable Javascript?
How do I write Testable Javascript?How do I write Testable Javascript?
How do I write Testable Javascript?
 
Getting your Hooks into Cordova
Getting your Hooks into CordovaGetting your Hooks into Cordova
Getting your Hooks into Cordova
 
Setting up your Multi Engine Environment - Apache Railo and ColdFusion
Setting up your Multi Engine Environment - Apache Railo and ColdFusionSetting up your Multi Engine Environment - Apache Railo and ColdFusion
Setting up your Multi Engine Environment - Apache Railo and ColdFusion
 

Recently uploaded

The Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptxThe Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptx
operationspcvita
 
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
zjhamm304
 
Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |
AstuteBusiness
 
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptxAI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
Sunil Jagani
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
DianaGray10
 
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
Fwdays
 
Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving
 
What is an RPA CoE? Session 2 – CoE Roles
What is an RPA CoE?  Session 2 – CoE RolesWhat is an RPA CoE?  Session 2 – CoE Roles
What is an RPA CoE? Session 2 – CoE Roles
DianaGray10
 
Session 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdfSession 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdf
UiPathCommunity
 
inQuba Webinar Mastering Customer Journey Management with Dr Graham Hill
inQuba Webinar Mastering Customer Journey Management with Dr Graham HillinQuba Webinar Mastering Customer Journey Management with Dr Graham Hill
inQuba Webinar Mastering Customer Journey Management with Dr Graham Hill
LizaNolte
 
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
Fwdays
 
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Ukraine
 
Getting the Most Out of ScyllaDB Monitoring: ShareChat's Tips
Getting the Most Out of ScyllaDB Monitoring: ShareChat's TipsGetting the Most Out of ScyllaDB Monitoring: ShareChat's Tips
Getting the Most Out of ScyllaDB Monitoring: ShareChat's Tips
ScyllaDB
 
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge GraphGraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
Neo4j
 
GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
Javier Junquera
 
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin..."$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
Fwdays
 
Principle of conventional tomography-Bibash Shahi ppt..pptx
Principle of conventional tomography-Bibash Shahi ppt..pptxPrinciple of conventional tomography-Bibash Shahi ppt..pptx
Principle of conventional tomography-Bibash Shahi ppt..pptx
BibashShahi
 
AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)
HarpalGohil4
 
"Choosing proper type of scaling", Olena Syrota
"Choosing proper type of scaling", Olena Syrota"Choosing proper type of scaling", Olena Syrota
"Choosing proper type of scaling", Olena Syrota
Fwdays
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
Ajin Abraham
 

Recently uploaded (20)

The Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptxThe Microsoft 365 Migration Tutorial For Beginner.pptx
The Microsoft 365 Migration Tutorial For Beginner.pptx
 
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...QA or the Highway - Component Testing: Bridging the gap between frontend appl...
QA or the Highway - Component Testing: Bridging the gap between frontend appl...
 
Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |Astute Business Solutions | Oracle Cloud Partner |
Astute Business Solutions | Oracle Cloud Partner |
 
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptxAI in the Workplace Reskilling, Upskilling, and Future Work.pptx
AI in the Workplace Reskilling, Upskilling, and Future Work.pptx
 
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectorsConnector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
Connector Corner: Seamlessly power UiPath Apps, GenAI with prebuilt connectors
 
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk"Frontline Battles with DDoS: Best practices and Lessons Learned",  Igor Ivaniuk
"Frontline Battles with DDoS: Best practices and Lessons Learned", Igor Ivaniuk
 
Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024Northern Engraving | Nameplate Manufacturing Process - 2024
Northern Engraving | Nameplate Manufacturing Process - 2024
 
What is an RPA CoE? Session 2 – CoE Roles
What is an RPA CoE?  Session 2 – CoE RolesWhat is an RPA CoE?  Session 2 – CoE Roles
What is an RPA CoE? Session 2 – CoE Roles
 
Session 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdfSession 1 - Intro to Robotic Process Automation.pdf
Session 1 - Intro to Robotic Process Automation.pdf
 
inQuba Webinar Mastering Customer Journey Management with Dr Graham Hill
inQuba Webinar Mastering Customer Journey Management with Dr Graham HillinQuba Webinar Mastering Customer Journey Management with Dr Graham Hill
inQuba Webinar Mastering Customer Journey Management with Dr Graham Hill
 
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba"NATO Hackathon Winner: AI-Powered Drug Search",  Taras Kloba
"NATO Hackathon Winner: AI-Powered Drug Search", Taras Kloba
 
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
GlobalLogic Java Community Webinar #18 “How to Improve Web Application Perfor...
 
Getting the Most Out of ScyllaDB Monitoring: ShareChat's Tips
Getting the Most Out of ScyllaDB Monitoring: ShareChat's TipsGetting the Most Out of ScyllaDB Monitoring: ShareChat's Tips
Getting the Most Out of ScyllaDB Monitoring: ShareChat's Tips
 
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge GraphGraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
GraphRAG for LifeSciences Hands-On with the Clinical Knowledge Graph
 
GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)GNSS spoofing via SDR (Criptored Talks 2024)
GNSS spoofing via SDR (Criptored Talks 2024)
 
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin..."$10 thousand per minute of downtime: architecture, queues, streaming and fin...
"$10 thousand per minute of downtime: architecture, queues, streaming and fin...
 
Principle of conventional tomography-Bibash Shahi ppt..pptx
Principle of conventional tomography-Bibash Shahi ppt..pptxPrinciple of conventional tomography-Bibash Shahi ppt..pptx
Principle of conventional tomography-Bibash Shahi ppt..pptx
 
AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)AWS Certified Solutions Architect Associate (SAA-C03)
AWS Certified Solutions Architect Associate (SAA-C03)
 
"Choosing proper type of scaling", Olena Syrota
"Choosing proper type of scaling", Olena Syrota"Choosing proper type of scaling", Olena Syrota
"Choosing proper type of scaling", Olena Syrota
 
AppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSFAppSec PNW: Android and iOS Application Security with MobSF
AppSec PNW: Android and iOS Application Security with MobSF
 

Itb 2021 - Bulding Quick APIs by Gavin Pickin

  • 1. Building Quick APIs by Gavin Pickin https://github.com/gpickin/itb2021-building-quick-apis
  • 2. Building Quick APIs by Gavin Pickin https://github.com/gpickin/itb2021-building-quick-apis
  • 3.
  • 4. Gavin Pickin Who am I? • Software Consultant for Ortus Solutions • Work with ColdBox, CommandBox, ContentBox APIs and VueJS every day! • Working with Coldfusion for 22 years • Working with Javascript just as long • Love learning and sharing the lessons learned • From New Zealand, live in Bakersfield, Ca • Loving wife, lots of kids, and countless critters @gpickin
  • 5. What is this talk? We will setup a secure API using fluent query language - and you’ll see how quick Quick development can be! We will use ● ColdBox ● ColdBox’s built in REST BaseHandler ● Route Visualizer ● CBSecurity ● Quick ORM
  • 6. What is this talk not? ● We will not be adding an API to a Legacy Site ● We will not be adding an API to a non ColdBox site ● It is not my CF Meetup Talk “Building APIs with ColdFusion Part 1” https://www.youtube.com/watch?v=UdgRt8HIKD0 BUT YOU COULD DO ALL OF THESE THINGS WITH THE KNOWLEDGE FROM THIS TALK
  • 7. Why should I give this talk? • I build a lot of APIs for Customers • I have seen many different ways to build an API • I’m going to share some of the lessons I have learned. • I try to present in step by step follow along later format that I think you’ll love • Hopefully you’ll learn something • Hopefully it will be useful.
  • 8. What is an API? API is an application programming interface More commonly when someone says API today they think of an JSON API, usually we hope for a REST API but not always. APIs can be xml, soap, but it’s very common to talk about JSON apis, and that’s what we’re talking about today. Although most of it is relatable.
  • 9. REST JSON API vs JSON API What is REST REST is acronym for REpresentational State Transfer. It is architectural style for distributed hypermedia systems and was first presented by Roy Fielding in 2000 in his famous dissertation. All REST are APIs but not all APIs are REST.
  • 10. Guiding Principles of REST ● Client–server ● Stateless ● Cacheable ● Uniform interface ● Layered system The key abstraction of information in REST is a resource. Any information that can be named can be a resource: a document or image, a temporal service, a collection of other resources, a non-virtual object (e.g. a person), and so on. REST uses a resource identifier to identify the particular resource involved in an interaction between components.
  • 11. What App are we working on? SOAPBOX API - An API for a twitter clone we built for our ColdBox Zero to Hero Training.
  • 12. Let’s do this!!! Start up CommandBox!!!
  • 13. Create a ColdBox App box coldbox create app soapbox-api This installs: ● ColdBox ● TestBox ● CommandBox-dotEnv ● CommandBox-cfconfig ● CommandBox-cfformat
  • 14. Let’s update our .env ● Change the connection string to match our DB - soapboxapi ● Change the DB_DATABASE to match our DB - soapboxapi ● Change ports, user name and password to match
  • 15. Let’s start our server That port matches the testbox runner in the box.json http://127.0.0.1:53163/ - our site http://127.0.0.1:53163/tests/runner.cfm - our tests box start port=53163
  • 16. Let’s plan our API What is an Example URL - /api/v01/users We’re going to make API a module with a V01 module inside of it. ● Add modules_app folder for this apps custom modules ● Add modules_app/api folder with ModuleConfig.cfc ● Add modules_app/api/modules/api-v01 folder with ModuleConfig.cfc API - V01
  • 17. Let’s build out our API V01 Module ● Add modules_app/api/modules/api-v01/config Folder with Router.cfc ● Setup the base route of “/” to go to main.index ● Add api-v01/handlers/main.cfc which extends coldbox.system.RestHandler ● Add index event in the main handler ● Index event should sets data in the response object to welcome the user to the api. http://127.0.0.1:53163/api/v01 API - V01
  • 18. Let’s plan our API USER resource GET /api/v02/users - get a list of users POST /api/v02/users - create a new user GET /api/v02/users/:userID - get a single user POST /api/v02/users/:userID - update a single user DELETE /api/v02/users/:userID - delete a single user API - V02
  • 19. Add User Resource to Router.cfc resources( resource = "users", handler = "users", parameterName = "userID", only = [ "index", "show", "create", "update", "delete" ] ); API - V02
  • 20. Let’s view our Routes box install route-visualizer API - V02
  • 21. Build our Users Handler function index( event, rc, prc ){ event.getResponse().setData( "List Users" ); } function create( event, rc, prc ){ event.getResponse().setData( "Create User" ); } API - V02 http://127.0.0.1:53163/api/v02/users
  • 22. Secure our CUD Actions Let’s use CBSecurity to lock down our non READ functionality ● CREATE ● UPDATE ● DELETE API - V03
  • 23. Install CBSecurity Add the Config to your config/ColdBox.cfc Add a JWT Secret incase .env has a blank one Add “api-v03:main.index” for “invalidAuthenticationEvent” Add “api-v03:main.index” for “invalidAuthorizationEvent” API - V03 box install cbsecurity
  • 24. Add Secured Metadata function create( event, rc, prc ) secured{ event.getResponse().setData( "Create Users" ); } API - V03
  • 26. Updating Redirects for API Authentication and Authorization Issues You should really setup your main ColdBox app for an HTML page and your API module for an API page Add settings to the API-v04 Module Config Add “api-v04:main.index” for “invalidAuthenticationEvent” Add “api-v04:main.index” for “invalidAuthorizationEvent” API - V04
  • 27. Updating Redirects for API Authentication and Authorization Issues http://127.0.0.1:53163/api/v04/users/?_method=post Now we are redirected to an API Handler You can add new methods for onInvalidAuth and or onInvalidAuthorization to be more specific in your responses API - V04
  • 28. Install and Configure Quick add a datasource definition in Application.cfc API - V05 this.datasource = "soapboxapi"; box install quick
  • 29. Create our Quick User Entity component extends="quick.models.BaseEntity" accessors="true" { property name="id" type="string"; property name="username" type="string"; property name="email" type="string"; property name="password" type="string"; } API - V05
  • 30. Quick is Smart ● Quick guesses the table is named Users because the entity is User ● Quick guesses the Primary Key is ID ● Quick uses the Datasource defined in the Application.cfc ● Quick can make a Virtual Service for us from a WireBox Injection property name="userService" inject="quickService:User@api-v05"; Quick does a lot for us API - V05
  • 31. Show a User function show( event, rc, prc ){ event.paramValue( "userID", 0 ); event.getResponse().setData( userService .findOrFail( rc.userID ) .getMemento() ); } API - V05
  • 33. What if we can’t find that User? http://127.0.0.1:53163/api/v05/users/x 404 response API - V05
  • 34. Return a list of Users function index( event, rc, prc ){ event.getResponse().setData( userService .asMemento() .get() ); } API - V06
  • 35. What is asMemento() asMemento() is a special function that tells quick you want to map over all of the objects in the array, and call getMemento() on all of the items .get().getMemento() doesn’t work because .get() returns an array As memento is essentially the following done for you. ...get().map( (item) => { return item.getMemento() } ); API - V06
  • 36. Reinit and check the API http://127.0.0.1:53163/api/v06/users/ API - V06
  • 37. Configuring Mementifier ● We don’t want to return all of the fields ● For example Password, even if it is encrypted https://github.com/coldbox-modules/mementifier API - V07
  • 38. Mementifier Inline Excludes API - V07 userService .asMemento( excludes=[ "password" ] ) .get() http://127.0.0.1:53163/api/v07/users
  • 39. Mementifier - Inline Includes API - V08 userService .asMemento( includes=[ "id", "username", "email" ], ignoreDefaults = true ) .get() http://127.0.0.1:53163/api/v08/users
  • 40. Mementifier - Entity Config API - V09 //models/User.cfc this.memento = { defaultIncludes=[ "username", "email" ], neverInclude=["password"] } http://127.0.0.1:53163/api/v09/users/2
  • 41. Mementifier - Inline Includes API - V10 function index( event, rc, prc ){ event.getResponse().setData( userService .asMemento( includes=[ “ID” ] ) .get() ); } http://127.0.0.1:53163/api/v10/users/
  • 42. Using Where to Filter Users API - V11 event.paramValue( "username_filter", "" ); event.getResponse().setData( userService .where( "username", "like", '%#rc.username_filter#%') .asMemento( includes="ID" ) .get() ); http://127.0.0.1:53163/api/v11/users?username_filter=gp
  • 43. Using Scope to Filter Users API - V12 //handler - use this userService.whereUsernameLike(rc.username_filter) // not this userService.where( "username", "like", '%#rc.username_filter#%') // models/User.cfc function scopeWhereUsernameLike( qb, filter ){ qb.where( "Username", "like", '%#filter#%' ) } http://127.0.0.1:53163/api/v12/users?username_filter=gp
  • 44. Using Dynamic Where to Match Users API - V13 userService .whereUsernameLike(rc.username_filter) .whereUsername(rc.username) .asMemento( includes="ID" ) .get() http://127.0.0.1:53163/api/v13/users?username_filter=gp
  • 45. Using When for Empty Variables API - V14 userService .whereUsernameLike(rc.username_filter) .when( len( rc.username ), function( q ) { q.whereUsername(rc.username) } .asMemento( includes="ID" ) .get() http://127.0.0.1:53163/api/v14/users?username_filter=gp
  • 46. Using When for Empty Variables API - V14 userService .whereUsernameLike(rc.username_filter) .when( len( rc.username ), function( q ) { q.whereUsername(rc.username) } .asMemento( includes="ID" ) .get() http://127.0.0.1:53163/api/v14/users?username=gp
  • 47. Using When for Empty Variables API - V14 userService .whereUsernameLike(rc.username_filter) .when( len( rc.username ), function( q ) { q.whereUsername(rc.username) } .asMemento( includes="ID" ) .get() http://127.0.0.1:53163/api/v14/users?username=gpickin
  • 48. Using ‘Or’ for Similar Filters API - V15 userService .whereUsernameLike(rc.username_filter) .when( len( rc.username ), function( q ) { q.whereUsername(rc.username) } .asMemento( includes="ID" ) .get() http://127.0.0.1:53163/api/v14/users?username=gpickin&username_filter=luis
  • 49. Using ‘Or’ for Similar Filters API - V15 userService .whereUsernameLike(rc.username_filter) .orWhere( function(q){ q.when( len( rc.username ), function( q2 ) { q2.orwhereUsername(rc.username) }) }) http://127.0.0.1:53163/api/v14/users?username=gpickin&username_filter=lui
  • 50. Setup Rant Resource API - V16 resources( resource = "rants", handler = "rants", parameterName = "rantID", only = [ "index", "show", "create", "update", "delete" ] );
  • 51. Setup Rant Handler API - V16 /** * Manage Rants API Handler * */ component extends="coldbox.system.RestHandler "{ /** * Display a list of Rants */ function index( event, rc, prc ){ event.getResponse().setData( "Show Rants"); } /** * Create a Rant */
  • 52. Test Rant Resource API - V16 http://127.0.0.1:53163/api/v16/rants? http://127.0.0.1:53163/api/v16/rants/6
  • 53. Create Rant Entity API - V17 component extends="quick.models.BaseEntity" accessors="true" { property name="id" type="string"; property name="body" type="string"; property name="createdDate" type="date"; property name="modifiedDate" type="date"; property name="userID" type="string"; }
  • 54. Inject Rant Service and Return Rants API - V17 property name="rantService" inject="quickService:Rant@api-v17"; function index( event, rc, prc ){ event.getResponse().setData( rantService .asMemento() .get() ); } http://127.0.0.1:53163/api/v17/rants
  • 55. Paginate the Rants Returned API - V18 http://127.0.0.1:53163/api/v17/rants ● We don’t want to return every record, it will only grow longer and longer over time, and performance will suffer ● ColdBox RESTBaseHandler already has Pagination information ● Let’s use Quick’s Paginate function
  • 56. Paginate the Rants Returned API - V18 http://127.0.0.1:53163/api/v18/rants event.getResponse().setData( rantService .asMemento() .paginate(1,10) );
  • 57. Paginate the Rants Returned API - V18 http://127.0.0.1:53163/api/v18/rants event.getResponse().setData( rantService .asMemento() .paginate(1,10) ); Why do we have 2 sets of Pagination Data???
  • 58. Use SetDataWithPagination API - V19 http://127.0.0.1:53163/api/v19/rants event .getResponse() .setDataWithPagination( rantService .asMemento() .paginate(1,10) );
  • 59. Return Rants User Information // Add User Relationship to Rant.cfc function user(){ return hasOne( "User@api-v20", "id", "userID" ); } API - V20 // Update Rants.cfc handler event.getResponse().setDataWithPagination( rantService .asMemento( includes="user" ) .paginate(1,10) );
  • 60. Return Rants User Information API - V20 http://127.0.0.1:53163/api/v20/rants
  • 61. Return Rants User Information API - V20 ● Nested Fields ● What if we just want 1 field? ● How is Mementifier getting the data?? http://127.0.0.1:53163/api/v20/rants
  • 62. Return Rants with SubSelect User Information API - V21 event.getResponse().setDataWithPagination( rantService .addSubselect( "username", "user.username") .asMemento() .paginate(1,10) ); http://127.0.0.1:53163/api/v21/rants
  • 63. Return a Count of Rants for Users API - V22 // Add Rants Relationship to models/User.cfc function rants(){ return hasMany( "Rant@api-v22", "userID", "id" ); } // Update users.cfc handler userService .withCount( "rants" ) .asMemento( includes=["ID","rantsCount"] ) .get() http://127.0.0.1:53163/api/v22/users
  • 64. Return Recent Rants for Users API - V23 //Router.cfc get( "/users/:userID/rants" , "userRants.index" ); // Handlers/userRants.cfc function index( event, rc, prc ){ event.paramValue( "userID", "" ); event.paramValue( "page", 1 ); event.paramValue( "per_page", 10 ); event.getResponse().setDataWithPagination ( rantService .where( "userID", rc.userID ) .orderBy( "createdDate", "desc" ) .asMemento() .paginate( rc.page, rc.per_page ) ); } http://127.0.0.1:53163/api/v23/users/2/rants
  • 65. Create a new Rant API - V24 http://127.0.0.1:53163/api/v24/rants?_method=post&userID=2&body=Adding%20a%20Rant function create( event, rc, prc ){ event.paramValue( "userID", "" ); event.paramValue( "body", "" ); userService .findOrFail( rc.userID ) .rants() .create( { body: rc.body } ); event.getResponse().setData( "Rant Created" ); }
  • 66. View the new Rant API - V24 http://127.0.0.1:53163/api/v24/users/2/rants
  • 67. What if we add an Empty Rant API - V24 http://127.0.0.1:53163/api/v24/rants?_method=post&userID=2&body= Not User Friendly
  • 68. Validate a new Rant API - V25 box Install cbvalidation
  • 69. Validate a new Rant - Handler Constraints API - V25 var validationResult = validate( target = rc, constraints = { body : { required : true } } ) if ( !validationResult.hasErrors() ) { // Normal API Response } else { event.getResponse() .setError( true ) .addMessage( validationResult.getAllErrors() ) .setStatusCode( 400 ) .setStatusText( "Validation error" ); } http://127.0.0.1:53163/api/v25/rants?_method=post&userID=2&body=
  • 70. Validate a new Rant - Entity Constraints API - V26 //Rant.cfc this.constraints = { body : { required : true }, userID: { required : true, type : "numeric" } }; // Handler/rants.cfc var rant = getInstance( "Rant@api-v26" ) .fill({ body: rc.body, userID: rc.userID }); validateOrFail( rant ); var user = userService.findOrFail( rc.userID ); rant.save(); event.getResponse().setData( "Rant Created" );
  • 71. Validate a new Rant - Entity Constraints API - V26 http://127.0.0.1:53163/api/v26/rants?_method=post&userID=2&body= Body is missing or empty
  • 72. Validate a new Rant - Entity Constraints API - V26 http://127.0.0.1:53163/api/v26/rants?_method=post&userID=xxx&body=MyRant UserID is not numeric
  • 73. Validate a new Rant - Entity Constraints API - V26 http://127.0.0.1:53163/api/v26/rants?_method=post&userID=0&body=MyRant UserID is numeric but not in User table
  • 74. Edit a Rant API - V27 event.paramValue( "rantID", 0 ); event.paramValue( "body", "" ); var rant = rantService.findOrFail( rc.rantID ) .fill({ body: rc.body }); validateOrFail( rant ); rant.save(); event.getResponse().setData( "Rant Updated" );
  • 75. Edit a Rant - Success API - V27 http://127.0.0.1:53163/api/v27/rants/178/?_method=put&body=Yes%20thats%20is%20right
  • 76. Edit a Rant - Missing Body API - V27 http://127.0.0.1:53163/api/v27/rants/178/?_method=put&body=
  • 77. Delete a Rant API - V28 function delete( event, rc, prc ){ event.paramValue( "rantID", 0 ); var rant = rantService.findOrFail( rc.rantID ).delete(); event.getResponse().setData( "Delete a Rant" ); }
  • 78. Delete a Rant that doesn’t Exist API - V28 http://127.0.0.1:53163/api/v28/rants/70000/?_method=delete
  • 79. Delete a Rant that does Exist API - V28 http://127.0.0.1:53163/api/v28/rants/7/?_method=delete
  • 80. Secure the Add / Edit Rant function create( event, rc, prc ) secured{ … } function update( event, rc, prc ) secured{ … } API - V29 NOT LOGGED IN http://127.0.0.1:53163/api/v29/rants/178/?_method=put&body=hiya
  • 81. Secure the Delete Rant with a Special Permission API - V29 function delete( event, rc, prc ) secured="RANT__DELETE"{ … } http://127.0.0.1:53163/api/v29/rants/7/?_method=delete NOT LOGGED IN
  • 82. Create a Generate JWT Endpoint //Router.cfc post( "/login", "session.create" ); delete( "/logout", "session.delete" ); //handlers/session.cfc function create( event, rc, prc ){ token = jwtAuth().fromuser( userService.findOrFail( 2 ) ); event.getResponse() .addMessage( "Token Created" ) .setData( token ); } function delete( event, rc, prc ){ jwtAuth().logout(); event.getResponse().setData( "Delete Session - Log Out" ); } API - V30
  • 83. Decorate User.cfc Entity for JWT API - V30 /** * A struct of custom claims to add to the JWT token */ struct function getJWTCustomClaims(){ return {}; } /** * This function returns an array of all the scopes that should be attached to the JWT * token that will be used for authorization. */ array function getJWTScopes(){ return []; }
  • 84. Hit Generate JWT Endpoint API - V30 http://127.0.0.1:53163/api/v30/login?_method=post
  • 85. Use JWT to Add a Rant API - V30
  • 86. But Wait, there’s more (I wish) I really wanted to show you more, but we don’t have all day. Join me at the CF Online Meetup where I do this again, maybe slower/better Join me for the October Ortus Webinar as we build on top of this. We might make a CFCasts series out of this if you like this style of content
  • 87. You can use images and add them opacity at 90%. This will blend the image with the gradient background. This is a good option for subtitles also. Testing APIs with TestBox with Javier Quintero Up Next
  • 88. E-mail: gavin@ortussolutions.com Twitter: @gpickin Gavin Pickin Senior Software Consultant Ortus Solutions, Corp https://github.com/gpickin/itb2021-building-quick-apis