@ -1,7 +1,212 @@
// setting DANGER_ADMIN=1 in the env enables developer and admin without logins
/ *
_ _WARNING _ _ : Early documentation . This should be treated as notes to
remind you of how to use the ` api/ ` handlers with this , rather than
a full guide .
This is a convenience library that helps with normalizing JSON API
requests and responses with [ Express . js ] ( https : //expressjs.com). The
` API ` class contains useful functions for redirecting , building replies ,
sending back errors , authenticating users , and validating forms . It is
primarily used in writing ` api/ ` handlers .
# # How ` api/ ` Handlers Run
There ' s a command in ` commands/api.js ` that runs everything , and contains a lot
of tricks for Express . js that you should study . You run this with the ` bando.js `
command runner like this :
` ` ` shell
node bando . js api
` ` `
This command then imports all of the files in ` api/ ` recursively , as well as
the ` socket/ ` handlers , and sets them up to handle requests . Requests are mapped
by URL to ` api/ ` by taking the ` .js ` off the file and using that as the URL . So
` api/running.js ` becomes ` /api/running ` .
# # Writing ` api/ ` Handlers
The code in ` api/ ` is mapped to URLs almost directly , so to create a new
URL just make a file in that directory . For example , if you want ` /api/running `
as a check if your server is running , then create the file ` api/running.js ` .
The easiest way to make this file is to use the ` node bando.js djent ` command like this :
` ` ` shell
node bando . js djent -- template static / djenterator / api . js -- output api / running . js
` ` `
This will use the template in ` static/djenterator/api.js ` to craft a starter file
for your ` api/running.js ` . If you want to change the output you can access
[ http : //127.0.0.1:5001/admin/#/djenterator/](http://127.0.0.1:5001/admin/#/djenterator/)
and visually choose the template to change , and edit the ` .json ` variables file it uses .
You should study this template output for all of the things you might need , although most
of it you ' ll strip out in this little tutorial .
# # Handling Requests
To handle a ` GET ` , ` POST ` , or other request you simply create an exported function that
matches the lowercase version of the name :
` ` ` javascript
export const get = async ( req , res ) => {
// do you thing here
}
` ` `
In the example file ` api/running.js ` you just created with ` bando.js djent `
you should see this function . Next you ' ll strip out everything that has
nothing to do with replying with a status .
# # Using ` class API `
You create the ` API ` class using the Express ` request ` and ` response `
objects in your ` api/ ` handler , then use the methods ` .reply() ` , ` .redirect() ` ,
or ` .error() ` to report status of the HTTP request . Let ' s strip down
your generated ` api/running.js ` file to the minimum necessary :
` ` ` javascript
import logging from '../lib/logging.js' ;
import { API } from '../lib/api.js' ;
const log = logging . create ( import . meta . url ) ;
export const get = async ( req , res ) => {
const api = new API ( req , res ) ;
try {
api . reply ( 200 , { message : "OK" } ) ;
} catch ( error ) {
log . error ( error ) ;
api . error ( 500 , error . message || "Internal Server Error" ) ;
}
}
` ` `
Here you can see I am only leaving logging , ` API ` , and returning simple replies
with ` api.reply(200, {message: "OK")) ` or a more complex error reply if that fails .
# # Authenticating Requests
Authentication for a handler is very simple and mostly done for you :
` ` ` javascript
// add this after the above get method
get . authenticated = true ;
` ` `
You "tag" any handlers you want protected with authentication by setting the ` authenticated `
variable on them . The ` commands/api.js ` runner then knows to demand authentication before
running this handler . When it is run you can then use ` API.user ` to get at the authenticated
user .
# # Form Validation
Validating forms in a server is very important , as you can ' t trust that any
browser actually performed the validation . The ` API ` class uses the wonderful
[ Laravel validation rules ] ( https : //laravel.com/docs/9.x/validation) via the
very nice [ Validation ] ( https : //www.npmjs.com/package/Validator) module by
[ jfstn ] ( https : //github.com/jfstn/Validator). These rules are also the same
as returned by the ` lib/ormish.js:Model.validation ` method . That means you
can :
1. Get your Model from ` lib/models.js ` .
2. Call ` TheModel.validation(rules) ` with empty '' for any rules you want filled in based on the database schema .
3. Pass those rules to ` API.validate() ` to check inputs match the Model ' s rules .
4. Return a ` API.validation_error() ` when they fail .
Here ' s an example taken from the ` api/register.js ` handler , but stipped down to
the essentials so it only validates a user form then returns the cleaned up form :
` ` ` javascript
import { User , Site , } from "../lib/models.js" ;
const rules = User . validation ( {
email : '' ,
full _name : '' ,
initials : 'required|alpha|max:3' ,
password _repeat : 'required|same:password' ,
password : '' ,
tos _agree : 'required|boolean|accepted' ,
} ) ;
export const post = async ( req , res ) => {
const api = new API ( req , res ) ;
const form = api . validate ( rules ) ;
if ( ! form . _valid ) {
return api . validation _error ( res , form ) ;
} else {
api . clean _form ( form , rules ) ;
api . reply ( 200 , { clean _user : form } ) ;
}
}
` ` `
# # A Good Validation Pattern
Validating forms and input is ver important , but you want to do it in a way that
doesn 't interfere with a user' s typing , yet still uses the client - side browser to
speed up the validation feedback . There ' s also the problem that _all _ form validation
systems seem to need the validation rules in both the backend and frontend of the
application .
I ' ve found the best pattern to handle all of these is to " validate on first submit , return
the rules for later submits . " It works like this :
1. On the first view of the form do _not _ provide any validation feedback . This removes the annoying noise of fields claiming inputs are wrong when they 're simply being worked on. For example, as someone types their email it' s annoying to flash it right / wrong until they ' re done .
2. Always validate based on the rules , and even better based on the database schema .
3. When this first validation fails , return a validation failure _and _ return the rules for the browser to use for later attempts .
4. The browser then has the rules , shows that there was an error , and can then provide immediate fast feedback to the user and block submits until its correct .
5. This also places the rules in only one place : the backend , where they ' re absolutely required .
# # Client Side Validation
The validation process I described needs one more component on the client side , which is
demonstrated in the ` client/pages/Registration.svelte ` page :
` ` ` javascript
const register = async ( ) => {
let [ status , data ] = await api . post ( "/api/register" , form ) ;
if ( status === 200 ) {
$user . new _registration = true ;
push ( '/' ) ;
} else {
form = Object . assign ( form , data ) ;
}
}
` ` `
The ` api.post("/api/register", form) ` then uses ` API.validation_error ` to return an
error response which contains the errors _and _ validation rules that the browser
should use . Remember , the ` api.post() ` above is from ` client/api.js:post ` while
the ` API.validation_error ` is from this file .
_ _ _BUG _ _ _ : Currently ` client/pages/Registration.svelte ` doesn ' t demonstrate the other part
of using ` client/api.js:validate ` to do more validations in the browser .
# # Going Further
Once you get this working that ' s mostly all there is to writing an ` api/ ` handler in
The Bandolier . You ' ll then want to learn how ` lib/ormish.js ` and ` lib/models.js ` works ,
and how to create new UIs in ` client/ ` or ` admin/ ` . On the client side you ' ll want to
study the ` client/api.js ` which is the counterpart to this module , and look at the
` client/components/FormField.svelte ` plus ` client/pages/Registration.svelte ` for other
examples .
* /
import Validator from 'Validator' ;
/ *
Used internally to handle security decisions based on whether someone
runs the app with ` DANGER_ADMIN=1 ` environment variable set or not .
Originally I was very loose in how this can be set , with sometimes ` DANGER_ADMING=true `
or ` DANGER_ADMIN="1" ` , but I ' ve since normalized that it will _only _ work if
` DANGER_ADMIN==="1" ` .
* /
export const developer _admin = process . env . DANGER _ADMIN === "1" ;