Functionality provided by server-side web frameworks may include the following:
Tying dynamic functionality to specific URLs.
Support for the basic HTTP request-response lifecycle.
Some kind of application context.
Integration with a templating framework.
Support for a persistence layer.
Persistence layer, used for storing long-term information which may be shared by multiple application servers.
Global to entire application. Good place to remember information like database connections and read-only info like configuration.
Information associated with a user (often tied to user via a cookie). Problematic if only in memory; avoid except for non-critical information.
Context associated with an individual request-response cycle. Can often store this information in some kind of request object.
Information stored on client. Browsers provide cookies, local storage (survives browser restarts) and session storage (does not survive browser restarts).
Express.js is built on top of nodejs HTTP facilities (which means that you may need to refer to nodejs HTTP docs).
Allows dispatching HTTP requests based on a modular routing framework.
Allows inserting middleware into the application's request-response cycle.
Allows plugging in multiple template engines to generate dynamic content.
A router allows specifying that a handler function (single or multiple) be run when the URL for a web request matches a particular route and method.
Matching route can be a fixed string or a pattern or a regular expression.
Routes set up using
//invoked for all requests app.use(HANDLER); //invoked for all requests with URL matching PATTERN app.use(PATTERN, HANDLER1,...); //invoked for request having METHOD (get, post, etc) //with URL matching PATTERN app.METHOD(PATTERN, HANDLER1, ...)
Routes are tried in order and first matching route chosen. Hence order of routes matter.
Express refers to handler functions as middleware as they
sit in the "middle" of a request-response cycle.
Handler function takes a req and res argument to access the incoming HTTP request and outgoing HTTP response as objects.
Optionally, handler functions can also take a next
argument;
calling next()
allows chaining to the next set of handlers for a
route which matches the request URL.
If method and pattern matches a particular handler which does not
call next()
, then subsequent matching handler will never be
called.
Special error hander which must be declared with four arguments
err
, req
, res
and next
.
Allow user login, logout and registration.
Allow CRUD functionality for user information.
Distinguish between admin
and non-admin
users.
Authentication via loginId
and password
, with
cookies used to maintain login sessions.
Associate an optional roles
property with each user. If "admin"
\(\in\) roles
, then the user is an admin
user, else the user
is a non-admin
user.
A user is logged in via a session cookie which identifies the user.
Creating a non-admin
user does not require authorization.
If a non-admin
user or user which is not logged in creates
a user, then the successfully created user is logged in.
If an admin
user creates a user, then the admin
user stays
logged in.
An admin
user can search for users, create admin
users as
well as update/delete other users.
A non-admin
user can only update/delete itself. It cannot
create an admin
user nor can it search for users.
GET /auth
:
Return services accessible to currently logged in user
(or anonymous user, if not logged in).
POST /auth
:
Create new user with params given in request body.
Returns user object in response body; also sets location
header to it.
POST /auth/login
:
Login using {loginId, password}
in request body. Returns
user object in response body.
GET /auth/
userId :
Returns user object in response body.
PATCH /auth/
userId :
Updates user object with update specified by request body.
Returns updated user object in response body.
DELETE /auth/
userId :
Removes user specified by userId.
POST /auth/
userId/logout
:
Logout currently logged in user userId.
GET /auth/query
:
Search users by optional query parameters; paginate results
using index
and count
query parameters.
Returns list of user objects in response body with links
possibly containing next
and prev
links.
JSON Schema is used for validation.
Any JSON data must be well-formed (meet JSON syntax restrictions),
but we need further validity constraints. For example, both
[ "hello", "world" ]
and [ 1, 2 ]
are well-formed JSON, but
only the latter can be valid as a 2D-point.
A JSON schema is a meta JSON document describing the validity constraints for JSON data.
A key property in a JSON schema is type
, which can be one
of string
, number
, integer
, boolean
, null
, object
or array
.
Depending on the value of type
, there can be other properties in
a JSON schema: for example:
type string
can have additional properties like minLength
, maxLength
,
pattern
, contentEncoding
, contentMediaType
,
contentSchema
.
type number
can have additional properties like minimum
,
exclusiveMinimum
, maximum
, exclusiveMaximum
,
multipleOf
.
type object
can have additional properties like properties
, required
,
etc.
type array
can have additonal properties like contains
, prefixItems
,
minContains
.
PHP's Opis JSON Schema is one of the best documents I've seen describing JSON schema.
Using JavaScript, rather than JSON syntax:
// a schema for a search query containing count and index // paging parameters + possible additional filter parameters const querySchema = { type: 'object', properties: { count: { type: 'integer', minimum: 0, }, index: { type: 'integer', minimum: 0, }, additionalProperties: true, }, };
JSON schema can be be used as a contract between an API producer and API consumer.
Widely used.
JSON schema is independent of implementation language; JSON schema libraries exist for most popular programming languages.
Since a JSON document (the schema) is used to constrain another JSON document (the data), sometimes it is difficult to distinguish between properties of the schema and properties of the data.
There are several meta-levels: the JSON schema itself is a meta-document
about the data, and the schema itself can contain meta-properties
about the schema itself like $schema
specifying the version of JSON
schema and $id
specifying its id.
Unfortunately, the standard does not specify exactly how validation errors should be handled.
JSON schema useful for validating API requests. However, similar validation often needs to be applied to user input. So DRY can be violated if sufficient care is not taken.
Ajv is a popular JavaScript JSON schema validator:
Compiles schema to JavaScript code.
Supports latest versions of the JSON Schema standard.
Unfortunately, it uses a mutable API.
Default error messages can be confusing.
Builds on earlier User Authentication code. All code in auth-ws project:
Define user types.
Annotated logs of command line interaction using curl as an HTTP client.
Define user types.
Implementation of web services.
In-memory sessions for tracking logins. Could have also used
an external library like express-session
.
User validation using JSON schema.
Command-line handling.
Tests using supertest.