express-sweet
v2.0.3
Published
EXPRESS SWEET is an extension of EXPRESS.
Downloads
84
Maintainers
Readme
EXPRESS SWEET
EXPRESS SWEET is an extension of EXPRESS.
- EXPRESS SWEET
EXPRESS SWEET Generator
Use the application generator tool, express-sweet-generator
, to quickly create an application skeleton.
- Install and launch the application generator as a global npm package.
npm install -g express-sweet-generator
- Display the command options with the -h option.
express-sweet -h Usage: express-sweet [options] [dir] Options: --version output the version number -o, --output <output> add output <module> support (esm|cjs) (defaults to cjs) -p, --port <port> application listening port (default: 3000) -f, --force force on non-empty directory -h, --help output usage information
- For example, the following creates an Express app named
myapp
. The app will be created in a folder namedmyapp
in the current working directory.express-sweet -o esm myapp
- Then install dependencies.
cd myapp/ npm install
- The skeleton uses a DB. Please create a DB with the following SQL.
CREATE DATABASE IF NOT EXISTS `sample_db` DEFAULT CHARACTER SET utf8mb4; USE `sample_db`; CREATE TABLE `user` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(30) NOT NULL, `email` varchar(255) NOT NULL, `password` varchar(100) NOT NULL, `icon` varchar(768) NOT NULL DEFAULT MD5(RAND()), `created` datetime NOT NULL DEFAULT current_timestamp(), `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `ukUserEmail` (`email`), UNIQUE KEY `ukUserIcon`(`icon`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `profile` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `userId` int(10) unsigned NOT NULL, `address` varchar(255) NOT NULL, `tel` varchar(14) NOT NULL, `created` datetime NOT NULL DEFAULT current_timestamp(), `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `ukProfileUserId` (`userId`), CONSTRAINT `fkProfileUser` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `comment` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `userId` int(10) unsigned NOT NULL, `text` text NOT NULL, `created` datetime NOT NULL DEFAULT current_timestamp(), `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), CONSTRAINT `fkCommentUser` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `book` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `userId` int(10) unsigned NOT NULL, `title` text NOT NULL, `created` datetime NOT NULL DEFAULT current_timestamp(), `modified` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(), PRIMARY KEY (`id`), UNIQUE KEY `ukBookTitle` (`userId`, `title`(255)), CONSTRAINT `fkBookUser` FOREIGN KEY (`userId`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO `user` (`id`, `email`, `password`, `name`, `icon`) VALUES (1, '[email protected]', 'password', 'Robin', '/upload/1.png'), (2, '[email protected]', 'password', 'Taylor', '/upload/2.png'); INSERT INTO `profile` (`userId`, `address`, `tel`) VALUES (1, '777 Brockton Avenue, Abington MA 2351', '202-555-0105'), (2, '30 Memorial Drive, Avon MA 2322', ''); INSERT INTO `comment` (`userId`, `text`) VALUES (1, 'From Robin #1'), (1, 'From Robin #2'), (2, 'From Taylor #1'); INSERT INTO `book` (`userId`, `title`) VALUES (1, 'Beautiful'), (1, 'Lose Yourself'), (2, 'When Im Gone');
- Set the DB connection method in
config/database.js
. For details, please refer to here.export default { development: { username: 'root', password: 'password', database: 'sample_db', host: 'localhost', dialect: 'mariadb' }, test: { username: 'root', password: 'password', database: 'sample_db', host: 'localhost', dialect: 'mariadb' }, production: { username: 'root', password: 'password', database: 'sample_db', host: 'localhost', dialect: 'mariadb' } }
- The DB to be accessed can be defined for each environment. Specify the environment in the
.env
fileNODE_ENV=development
- Run the application with the following command.
npm start
- Then, load
http://localhost:3000/
in your browser to access the app.
The generated app has the following directory structure:. ├── .env ├── app.js ├── ecosystem.config.js ├── nginx.sample.conf ├── package.json ├── bin │ └── www ├── client │ ├── package.json │ ├── webpack.config.js │ └── src ├── config │ ├── authentication.js │ ├── config.js │ ├── database.js │ └── view.js ├── exceptions │ └── UserNotFound.js ├── models │ ├── BookModel.js │ ├── CommentModel.js │ ├── ProfileModel.js │ └── UserModel.js ├── public │ ├── build │ └── upload ├── routes │ ├── login.js │ ├── users.js │ └── api │ └── users.js ├── shared │ ├── CustomValidation.js │ └── empty.js └── views ├── edit-personal.hbs ├── error.hbs ├── login.hbs ├── personal.hbs ├── users.hbs ├── layout │ └── default.hbs └── partials └── .gitkeep
API
Environment variable
If you set the environment variable file in env_path of config/config.js
, the value of the environment variable file will be set automatically in process.env.
The NODE_ENV
environment variable is required. If omitted, development
is used.
You can access the environment variables yourself and perform your own checks and logic, as in the following example.
if (process.env.NODE_ENV === 'development')
;
Base configuration
The basic configuration of EXPRESS SWEET is defined in the config/config.js
file.
Click here to download a sample ESM configuration and here to download a sample CJS configuration.
env_path: string
The path to the environment configuration file (.env
).
When you start the EXPRESS SWEET application, the contents of the environment configuration file are automatically read and saved inprocess.env
.
The default is.env
.cors_enabled: boolean
Set to true to allow requests from another domain to the application.
The default isfalse
.max_body_size: string|number
Controls the maximum request body size.
If this is a number, then the value specifies the number of bytes.
if it is a string, the value is passed to the bytes library for parsing.
The default is 100kb.router_dir: string
The directory path where the routes module is located. The default is theroutes/
directory directly under the application root.default_router: string
EXPRESS SWEET can be told to load a default router when a URI is not present, as will be the case when root URL (/
) is requested.
For example, to specify a default route, setdefault_router
as follows.
Where blog is the name of the router module you want used.
Next, create theroutes/blog.js
module.In the following example, requesting the root URL (
/
) will result in "Hello World".import {Router} from 'express'; const router = Router(); router.get('/', (req, res) => { res.send('Hello World'); }); export default router;
rewrite_base_url: (baseUrl: string) => string
This is a hook that rewrites the base URL.
If you want to rewrite the app.locals.baseUrl property and the view's baseUrl variable, use this hook to return a new base URL.
The default value is the referrer's origin (eg https://example.com).
In this example,https://example.com/admin
is used as the base URL.rewrite_base_url: baseUrl => { return `${baseUrl}/admin`; }
is_ajax: (req: express.Request) => boolean
How to determine if it is an ajax request.
The default is that if there is an XMLHttpRequest in the request header (req.xhr
) returnstrue
.For example, if there is no XMLHttpRequest in req(
express.Request
) and the Ajax endpoint starts with /api, a custom Ajax decision can be made likereturn /^\/api\//.test(req.path)
.is_ajax: req => { // If the request URL begins with /api, it is assumed to be Ajax. return /^\/api/.test(req.path); // return !!req.xhr; }
hook_handle_error: (err: any, req: express.Request, res: express.Response, next: express.NextFunction) => void
Hooks the default behavior on request errors.
If unset, simply returns an error HTTP status. (res.status(err.status||500).end();
)hook_handle_error: (err, req, res, next) => { if (err.status === 404) // If the URL cannot be found, a 404 error screen (views/error-404.hbs) is displayed. res.render('error-404'); else // For other errors, unknown error screen (views/error-unknown.hbs) is displayed. res.render('error-unknown'); },
Routing
Routing refers to determining how an application responds to a client request to a particular endpoint, which is a URI (or path) and a specific HTTP request method (GET, POST, and so on).
Each route can have one or more handler functions, which are executed when the route is matched.
Route definition takes the following structure.
- app is an instance of express.
- METHOD is an HTTP request method, in lowercase.
- PATH is a path on the server.
- HANDLER is the function executed when the route is matched.
import express from 'express';
const app = express();
app.METHOD(PATH, HANDLER)
Basic Routing
All routes are defined in your route files, which are located in the routes/
directory.
These files are automatically mapped by EXPRESS SWEET
to the route files specified in the URI and the route handlers defined in the route files.
The following examples illustrate defining simple routes.routes/user.js
responds to GET /user
requests.
import {Router} from 'express';
const router = Router();
router.get('/', (req, res) => {
res.send('Hello World');
});
export default router;
Nested Routing
The router supports nested files.
If you create a nested folder structure files will be automatically routed in the same way still.routes/api/users.js
responds to GET /api/users
requests.
import {Router} from 'express';
const router = Router();
router.get('/', (req, res) => {
res.send('Hello World');
});
export default router;
Default Routing
EXPRESS SWEET can be told to load a default router when a URI is not present, as will be the case when only your site root URL (/
) is requested.
To specify a default router, open your config/config.js
file and set this variable:
default_router: '/blog'
Where blog is the name of the router module you want used.
Next, create the routes/blog.js
module.
import {Router} from 'express';
const router = Router();
router.get('/', (req, res) => {
res.send('Hello World');
});
export default router;
Now when you request the root URL (/
), you will see "Hello World".
Routing Methods
Express supports the following routing methods corresponding to the HTTP methods of the same names. For more details about routing, see the Exprees’s Routing Guide.
- checkout
- copy
- delete
- get
- head
- lock
- merge
- mkactivity
- mkcol
- move
- m-search
- notify
- options
- patch
- post
- purge
- put
- report
- search
- subscribe
- trace
- unlock
- unsubscribe
View
EXPRESS SWEET uses Handlebars as its view template engine.
This section describes the basic usage of the view on EXPRESS SWEET.
See the here for more information on how to use Handlebars.
Configuration
The view (template engine) configuration is defined in the config/view.js
file.
Click here to download a sample ESM configuration and here to download a sample CJS configuration.
views_dir: string
The directory path where the view will be placed.
The default is theviews/
directory directly under the application root.partials_dir: string
The directory path where the reusable templates are located.
The default is theviews/partials/
directory directly under the application root.
Reusable templates are automatically loaded when you launch the EXPRESS SWEET application and can be called from other templates.layouts_dir: string
The directory path where the reusable base template is located.
The default is theviews/layout/
directory directly under the application root.default_layout: string
The path to the layout file used by default.
The default is theviews/layout/default.*
File directly under the application root.
The extension of the default layout file is replaced with the extension specified byextension
.extension: string
Extension for templates and partials files.
The default is.hbs
.beforeRender: (req: express.Request, res: express.Response) => Promise<void>|void
Hook function just before the view is rendered.
For example, you can set your own local variables that can be used within the view.beforeRender: (req, res) => { res.locals.extra = 'Extra'; }
Syntax
To mark where layout should insert page.
{{{body}}}
To declare a block placeholder in layout.
{{{block "script"}}}
To define block content in a page.
{{#contentFor "script"}}
CONTENT HERE
{{/contentFor}}
Layout
There are three ways to use a layout, listed in precedence order.
Declarative within a page. Use handlebars comment.
{{!< LAYOUT}}
Layout file resolution.
If path starts with '.' LAYOUT is relative to template Else If `layoutsDir` is set LAYOUT is relative to `layoutsDir` Else LAYOUT from path.resolve(dirname(template), LAYOUT)
As an option to render.
Do not use this option in conjunction with passing user submitted data to res.
render e.g. res.render('index', req.query).
This allows users to read arbitrary files from your filesystem.res.render('veggies', { title: 'My favorite veggies', veggies: veggies, layout: 'layout/veggie' });
This option also allows for layout suppression (both the default layout and when specified declaratively in a page) by passing in a falsey Javascript value as the value of the layout property.
res.render('veggies', { title: 'My favorite veggies', veggies: veggies, layout: null // render without using a layout template });
Layout file resolution.
If path starts with '.' layout is relative to template Else If `layoutsDir` is set layout is relative to `layoutsDir` Else layout from path.resolve(viewsDir, layout)
Lastly, use defaultLayout if specified in hbs configuration options.
Layouts can be nested: just include a declarative layout tag within any layout template to have its content included in the declared "parent" layout.
Be aware that too much nesting can impact performances, and stay away from infinite loops.
Comparison Helper
eq()
Determine whether or not two values are equal (===).Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the value and type are the same, false if they are different.{{eq val1 val2}} {{#if (eqw val1 val2)}}...{{/if}}
- val1: any
eqw()
Determine whether or not two values are equal (==) i.e. weak checking.Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the values are the same, false if they are different.{{eqw val1 val2}} {{#if (eqw val1 val2)}}...{{/if}}
- val1: any
neq()
Determine whether or not two values are not equal (!==).Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the value and type are different, false if they are the same.{{neq val1 val2}} {{#if (neq val1 val2)}}...{{/if}}
- val1: any
neqw()
Determine whether or not two values are not equal (!=) weak checking.Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the values are different, false if they are the same.{{neqw val1 val2}} {{#if (neqw val1 val2)}}...{{/if}}
- val1: any
lt()
Check for less than condition (a < b
).Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the first value is less than the second value, false otherwise.{{lt val1 val2}} {{#if (lt val1 val2)}}...{{/if}}
- val1: any
lte()
Check for less than or equals condition (a <= b
).Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the first value is less than or equal to the second value, false otherwise.{{lte val1 val2}} {{#if (lte val1 val2)}}...{{/if}}
- val1: any
gt()
Check for greater than condition (a > b).Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the first value exceeds the second value, false otherwise.{{gt val1 val2}} {{#if (gt val1 val2)}}...{{/if}}
- val1: any
gte()
Check for greater than or equals condition (a >= b).Parameters:
- val1: any
First value to be compared with second. - val2: any
Second value to be compared with first.
Return:
boolean
Returns true if the first value is greater than or equal to the second value, false otherwise.{{gte val1 val2}} {{#if (gte val1 val2)}}...{{/if}}
- val1: any
not()
Logical NOT of any expression.Parameters:
- val: any
Any expression.
Return:
boolean
Returns the logical negation of the value.{{not val}} {{#if (not (eq val1 val2))}}...{{/if}}
- val: any
ifx()
Helper to imitate the ternary?:
conditional operator.Parameters:
- condition: boolean
Satisfying condition for getting first value. Either true of false. - val1: any
First value to be displayed as result. - val2: any
Second value to be displayed as result. Defaults to an empty string.
Return:
any
Returns the result of the ternary operator.{{ifx true val1 val2}} {{ifx false val1 val2}} {{ifx (eq val1 val2) val3 val4}} {{ifx (not (eq val1 val2)) val3 val4}} {{ifx true val}} {{ifx false val}}
- condition: boolean
empty()
Check if it is empty.
If the value is an array, returns true if there are no elements.
If the value is a string, the leading and trailing spaces are trimmed and then checked.Parameters:
- value: any
Character strings, arrays, objects, etc. to be checked.
Return:
boolean
Returns true if the value is empty, false otherwise.{{empty val}} {{#if (empty val)}}...{{/if}}
- value: any
notEmpty()
Check that it is not empty.
If the value is an array, returns true if there is an element.
If the value is a string, the leading and trailing spaces are trimmed and then checked.Parameters:
- val: any
Character strings, arrays, objects, etc. to be checked.
Return:
boolean
Returns true if the value is not empty, false otherwise.{{notEmpty val}} {{#if (notEmpty val)}}...{{/if}}
- val: any
count()
Determine the length of an array.Parameters:
- items: any[]
Array whose elements to be counted.
Return:
number|false
Returns the length of the array if the value is an array, false if the value is not an array.{{count val}}
- items: any[]
and()
Returns the boolean AND of two or more parameters passed i.e it is true iff all the parameters are true.Parameters:
- ...params: any[]
Any number of boolean parameters.
Return:
boolean
Returns the result of the logical product.{{and val1 val2}} {{#if (and val1 val2)}}...{{/if}}
- ...params: any[]
or()
Returns the boolean OR of two or more parameters passed i.e it is true if any of the parameters is true.Parameters:
- ...params: any[]
Any number of boolean parameters.
Return:
boolean
Returns the result of the OR.{{or val1 val2}} {{#if (or val1 val2)}}...{{/if}}
- ...params: any[]
coalesce()
Returns the first non-falsy value from the parameter list.
Works quite similar to the SQL'sCOALESCE()
function, but unlike this checks for the first non-false parameter.Parameters:
- ...params: any[]
Any number of parameters.
Return:
any
Returns the first non-false element of the parameter.{{coalesce val1 val2}}
- ...params: any[]
includes()
Returns boolean if the array contains the element strictly or non-strictly.Parameters:
- items: any[]
The array. - val: any
The value to be checked for existence in the array. - strict: boolean
false
for non-strict checking.true
by default.
Return:
boolean
Returns true if the array contains the specified value, false otherwise.{{includes [1, 2, 3] 2}} {{includes [1, 2, 3] 2 true}} {{#if (includes [1, 2, 3] 2)}}...{{/if}} {{ifx (includes [1, 2, 3] 2) true false}}
- items: any[]
regexMatch()
Returns true if the given str matches the given regex.Parameters:
- val: string
The string against which to match the regular expression. - pattern: string
The text of the regular expression. - flags?: string
Regular expression flags, such as global and case-insensitive searches. The default is none (undefined).
Return:
boolean
true if there is a match between the regular expression and the string str. Otherwise, false.{{regexMatch 'Hello, world!' 'Hello'}} {{regexMatch 'Hello, world!' 'Hello' 'i'}} {{#if (regexMatch 'Hello, world!' 'Hello')}}...{{/if}}
- val: string
HTML Helper
cacheBusting()
Returns the Assets path containing the file update time parameter.Parameters:
- filePath: string
Paths of Assets files such as CSS and JS in public directories. - baseUrl: string
Application Origin URL. The default is none (undefined
).
Return:
string
Returns the Assets file path with the update date and time parameters.{{!-- results in: example.com/assets/style.css?1620526340463 --}} {{cacheBusting '/assets/style.css' '//example.com'}}
- filePath: string
Object Helper
jsonStringify()
Stringify an object using JSON.stringify.Parameters:
- val: any
The value to convert to a JSON string. - indent: number
The number of space characters to use as whitespace.
Return:
string
A JSON string representing the given value, or undefined.{{jsonStringify val}}
- val: any
jsonParse()
Parses the given string using JSON.parse.Parameters:
- val: any
Object to stringify.
Return:
any
JavaScript value or object described by a string.{{jsonParse val}}
- val: any
String Helper
replace()
Returns a new string with some or all matches of a pattern replaced by a replacement.Parameters:
- val: string
String. - find: string
The string to be replaced. - replace: string
The string to replace.
Return:
string
Character string after replacement.{{replace 'The quick brown fox jumps over the lazy dog. If the dog reacted, was it really lazy?' 'dog' 'monkey'}}
- val: string
split()
Splitstring
by the givencharacter
.Parameters:
- val: string
String. - separator: string
A character that delimits the substrings in this string. Default is a comma.
Return:
string[]
An Array of strings, split at each point where the separator occurs in the given string. The default is a comma.{{split "a,b,c" ","}} {{#each (split list ',')}}<div>{{this}}</div>{{/each}}
- val: string
formatBytes()
Convert bytes to just the right units(KB, MB, GB, TB, PB, EB, ZB, YB).Parameters:
- bytes: number
Bytes. - decimals?: number
Number of decimal places to display. Default is 0.
Return:
string
Returns a value with units.{{!-- results in: 1 KB --}} {{formatBytes 1024}} {{!-- results in: 1.21 KB --}} {{formatBytes 1234 2}} {{!-- results in: 1.205 KB --}} {{formatBytes 1234 3}} {{!-- results in: 0 Bytes --}} {{formatBytes 0}}
- bytes: number
Date Helper
formatDate()
Use moment to format the date.Parameters:
- format: string
A format string based onmoment
. - date: string
Date string to format. - locale: string|string[]
Language or language-country locale string (or array of strings) available in https://github.com/moment/moment/tree/develop/locale.
Return:
string
Returns formatted date.{{!-- results in: 2021/10/24 --}} {{formatDate 'YYYY/MM/DD' "2021-10-24T02:13:06.610Z"}} {{!-- results in: 2021/10/24 --}} {{formatDate 'YYYY/MM/DD' "2021-10-24T02:13:06.610Z" 'jp'}} {{!-- results in: 2021/10/24 --}} {{formatDate 'YYYY/MM/DD' "2021-10-24T02:13:06.610Z" 'es'}}
- format: string
Number Helper
number2locale()
Returns the language-sensitive representation of a number as a string.Parameters:
- val: number|string
Target number or numeric string. - locales: string|undefined
A string with a BCP 47 language tag, or an array of such strings. Corresponds to thelocales
parameter of the Intl.NumberFormat() constructor. In implementations without Intl.NumberFormat support, this parameter is ignored and the host's locale is usually used.
Return:
string
A string with a language-sensitive representation of the given number.{{!-- results in: 123,456.789 --}} {{number2locale 123456.789}} {{!-- German uses comma as decimal separator and period for thousands. --}} {{!-- results in: 123.456,789 --}} {{number2locale 123456.789 'de-DE'}}
- val: number|string
Math Helper
add()
Calculates the sum of two numbers.Parameters:
- val1: number|string
The first number. - val2: number|string
The second number.
Return:
number
{{add 1 2}}
- val1: number|string
sub()
Calculates the difference of the given values.Parameters:
- val1: number|string
The first number. - val2: number|string
The second number.
Return:
number
{{sub 5 2}}
- val1: number|string
multiply()
Calculate the multiplication of the given values.Parameters:
- val1: number|string
The first number. - val2: number|string
The second number.
Return:
number
{{multiply 5 2}}
- val1: number|string
divide()
Compute the division of the given values.Parameters:
- val1: number|string
The first number. - val2: number|string
The second number.
Return:
number
{{divide 10 2}}
- val1: number|string
ceil()
Round up the value.Parameters:
- val: number|string
Number to be rounded to nearest greater integer.
Return:
number
{{ceil 5.6}}
- val: number|string
floor()
Rounds down a number.Parameters:
- val: number|string
Number to be rounded to nearest lower integer.
Return:
number
{{floor 5.6}}
- val: number|string
abs()
Returns an absolute value.Parameters:
- val: number|string
Number to perform absolute value operation on.
Return:
number
{{abs -5.6}}
- val: number|string
Model
Models provide a way to interact with a specific table in your database.
EXPRESS SWEET provides a Sequelize based model class that offers some great features, including.
- Automatic database connection.
- Basic CRUD methods.
- And more.
This class provides a solid base from which to build your own models, allowing you to rapidly build out your application’s model layer.
Configuration
The database configuration is defined in the config/database.js
file.
Click here to download a sample ESM configuration and here to download a sample CJS configuration.
username: string
The username which is used to authenticate against the database.password: string|null
The password which is used to authenticate against the database.
The default is no password (null
).database: string
The name of the database.host: string
The host of the relational database.port: number|null
The port of the relational database.
The default is automatic selection (null
).dialect: string
The dialect of the database you are connecting to.
One ofmariadb
,mysql
,postgres
,sqlite
andmssql
.logging: boolean|(...message: any[]) => void
Set true to output the executed query etc. to the log.
The default is no log output (false
).timezone: string
Time zone dedicated to writing to the database.
For example, for the Japanese time zone, do the following.timezone: '+09:00'
Accessing Model
Place the model in the models/
directory of the root directory.
When you load the model, you have immediate access to the model's functions for working with the database.
import BookModel from '../models/BookModel';
// INSERT INTO book (title) VALUES ('Beautiful')
await BookModel.create({title: 'Beautiful'});
// SELECT * FROM book
await BookModel.findAll();
// UPDATE book SET title = 'Beautiful' WHERE id= 1
await BookModel.update({title: 'Beautiful'}, {where: {id: 1}});
// DELETE FROM book WHERE id= 1
await BookModel.destroy({where: {id: 1}});
Creating Model
To take advantage of EXPRESS SWEET’s model, you would simply create a new model class that extends database/Model
.
This class provides convenient access to the database connection, the Query Builder, and a number of additional convenience methods.
For more information, see reference.
import * as expressExtension from 'express-sweet';
export default class extends expressExtension.database.Model {
static get table() {
return 'user';
}
static get attributes() {
return {
id: {
type: this.DataTypes.INTEGER,
primaryKey: true,
autoIncrement: true
},
name: this.DataTypes.STRING,
email: this.DataTypes.STRING,
password: this.DataTypes.STRING,
icon: this.DataTypes.STRING,
created: this.DataTypes.DATE,
modified: this.DataTypes.DATE
};
}
}
Database class extends sequelize.Sequelize
This is the class that makes the database connection.
See here for other available methods.
The database connection is automatic when the model is loaded.
The configuration related to the database connection is defined in the config/database.js
file.
For more information on database configuration, see Database configuration.
Instance Methods
public constructor()
Instantiate sequelize with name of database, username and password.public isConnect()
Test the connection by trying to authenticate. It runsSELECT 1+1 AS result
query.Return:
Promise<boolean>
Returnstrue
if it can connect to the database,false
if it cannot.import * as expressExtension from 'express-sweet'; await expressExtension.database.Database.isConnect();
Model Class extends sequelize.Model
This is a class that abstracts the tables in the database.
See here for more information on the methods and properties available in your model.
Class Properties
protected static table: string
The name of the table that the model accesses. This member must be defined in a subclass.import * as expressExtension from 'express-sweet'; export default class extends expressExtension.database.Model { static get table() { return 'user'; } }
protected static attributes: sequelize.ModelAttributes
List of columns in the table accessed by this model. This member must be defined in a subclass.import * as expressExtension from 'express-sweet'; export default class extends expressExtension.database.Model { static get attributes() { return { id: { type: this.DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, name: this.DataTypes.STRING, email: this.DataTypes.STRING, password: this.DataTypes.STRING, icon: this.DataTypes.STRING, created: this.DataTypes.DATE, modified: this.DataTypes.DATE }; } }
public static readonly DataTypes: {[key: string]: any}
A convenience class holding commonly used data types.
This is an alias forsequelize.DataTypes
.
See here for details.{id: this.DataTypes.INTEGER}
public static readonly Op: {[key: string]: any}
Operator symbols to be used when querying data.
This is an alias forsequelize.Op
.
See here for details.import BookModel from '../models/BookModel'; // SELECT * FROM book WHERE title = 'Beautiful' AND genre = 'Nonfiction'; BookModel.findOne({ where: { [BookModel.Op.and]: [ {title: 'Beautiful'}, {genre: 'Nonfiction'} ] } }); // SELECT * FROM book WHERE title = 'Beautiful' OR title = 'Lose Yourself'; BookModel.findAll({ where: { [BookModel.Op.or]: [ {title: 'Beautiful'}, {title: 'Lose Yourself'} ] } }); // DELETE FROM user WHERE name = 'Beautiful' OR name = 'Lose Yourself'; BookModel.destroy({ where: { title: {[BookModel.Op.or]: ['Beautiful', 'Lose Yourself']} } });
public static readonly fn: (fn: string, ...args: unknown[]) => any
Creates an object representing a database function.
This can be used in search queries, both in where and order parts, and as default values in column definitions.
If you want to refer to columns in your function, you should use sequelize.col, so that the columns are properly interpreted as columns and not a strings.
See here for details.import BookModel from '../models/BookModel'; // SELECT upper(`title`) AS `title` FROM `book` AS `book`; const books = await BookModel.findAll({ attributes: [[BookModel.fn('upper', BookModel.col('title')), 'title']], raw: true });
public static readonly col: (col: string) => any
Creates an object which represents a column in the DB, this allows referencing another column in your query.
This is often useful in conjunction with sequelize.fn, since raw string arguments to fn will be escaped.
See here for details.public static readonly literal: (val: string) => any
Creates an object representing a literal, i.e. something that will not be escaped.
See here for details.import BookModel from '../models/BookModel'; // SELECT `id`, `title`, (SELECT COUNT(*) FROM comment WHERE comment.bookId = book.id) AS `count` FROM `book` AS `book`; const books = await BookModel.findAll({ attributes: [ 'id', 'title', [BookModel.literal(`(SELECT COUNT(*) FROM comment WHERE comment.bookId = book.id)`), 'count'] ], raw: true });
public static readonly where: (attr: sequelize.AttributeType, comparator: string, logic: sequelize.LogicType) => sequelize.Utils.Where
A way of specifying attr = condition.
The attr can either be an object taken from Model.rawAttributes (for example Model.rawAttributes.id or Model.rawAttributes.name).
The attribute should be defined in your model definition.
The attribute can also be an object from one of the sequelize utility functions (sequelize.fn, sequelize.col etc.)
For string attributes, use the regular {where: {attr: something}} syntax.
If you don't want your string to be escaped, use sequelize.literal. See here for details.import BookModel from '../models/BookModel'; // SELECT `title` FROM `book` AS `book` WHERE CHAR_LENGTH(`title`) <= 10; const books = await BookModel.findAll({ attributes: ['title'], where: BookModel.where( BookModel.fn('CHAR_LENGTH', BookModel.col('title')), {[BookModel.Op.lte]: 10} ), raw: true });
public static readonly QueryTypes: {[key: string]: string}
An enum of query types used bysequelize.query
.
See here for details.public static readonly Transaction: (typeof sequelize.Transaction)
Reference to sequelize.Transaction.
This includes properties such as isolation level enums used with the transaction option.const BookModel = require('../models/BookModel'); BookModel.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED" BookModel.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED" BookModel.Transaction.ISOLATION_LEVELS.REPEATABLE_READ // "REPEATABLE READ" BookModel.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE"
Class Methods
public static association()
Associate the model.
Define associations with other models such ashasOne
,hasMany
,belongsTo
,belongsToMany
.
This method is called automatically from within themount
method, so you don't have to run it yourself.
See the here for more information.
If you omit the alias (as
) option, the associated name will be hasOne, singular for belongsTo, and plural for hasMany.import * as expressExtension from 'express-sweet'; import ProfileModel from './ProfileModel'; export default class extends expressExtension.database.Model { static association() { // User has one profile. this.hasOne(ProfileModel, { foreignKey: 'userId', // profile.userId sourceKey: 'id', // user.id as: 'profile' }); } }
public static begin()
Starts a transaction and returns a transaction object to identify the running transaction.
This is an alias for thesequelize.Sequelize.transaction()
method.
See here for details.Parameters:
- options?: sequelize.TransactionOptions
Options provided when the transaction is created.
Return:
Promise<sequelize.Transaction>
Returns a transaction object to identify the transaction being executed.Simple transaction usage example.
import BookModel from '../models/BookModel'; let transaction; try { transaction = await BookModel.begin(); const book = await BookModel.create({title: 'Beautiful'}, {transaction}); await transaction.commit(); } catch { if (transaction) await transaction.rollback(); }
You can also use transaction options.
import BookModel from '../models/BookModel'; let transaction; try { transaction = await BookModel.begin({ isolationLevel: BookModel.Transaction.ISOLATION_LEVELS.REPEATABLE_READ, type: BookModel.Transaction.TYPES.DEFERRED, }); const book = await BookModel.findOne({where: {id: 1}}, {transaction}); book.title = 'Beautiful'; await book.save({transaction}); await transaction.commit(); // Load updated data. await book.reload(); } catch { if (transaction) await transaction.rollback(); }
- options?: sequelize.TransactionOptions
public static create()
Builds a new model instance and callssave
on it.
See here for details.Parameters:
- values: object
Hash of data values to create new record with. - options: object
Build and query options.
Return:
Promise<Model>
Returns a model that contains the data for the added record.import BookModel from '../models/BookModel'; // INSERT INTO `book` (`id`,`title`) VALUES (DEFAULT,'Beautiful'); await BookModel.create({title: 'Beautiful'});
- values: object
public static save()
Validates this instance, and if the validation passes, persists it to the database.
See here for details.Parameters:
- options: object
save options.
Return:
Promise<Model>
Returns a model that contains data for manipulated records such as add and update.import BookModel from '../models/BookModel'; // INSERT INTO `book` (`id`,`title`) VALUES (DEFAULT,'Beautiful'); const book = BookModel.build({title: 'Beautiful'}); await book.save(); // UPDATE `book` SET `title`='Lose Yourself' WHERE `id` = 1; book.title = 'Lose Yourself'; await book.save();
- options: object
public static findOne()
Search for a single instance.
Returns the first instance found, or null if none can be found.
See here for details.Parameters:
- options: object
A hash of options to describe the scope of the search.
Return:
Promise<Model|null>
Returns a Model containing the first data found in the database.import BookModel from '../models/BookModel'; // SELECT `id`, `title`, `created`, `modified` FROM `book` AS `book` LIMIT 1; await BookModel.findOne();
- options: object
public static findAll()
Search for multiple instances.
See here for details.Parameters:
- options: object
A hash of options to describe the scope of the search.
Return:
Promise<Array<Model>>
Returns a model containing the data found in the database.import BookModel from '../models/BookModel'; // SELECT `id`, `title`, `created`, `modified` FROM `book` AS `book` WHERE `book`.`title` LIKE 'Beautiful%'; await BookModel.findAll({ where: { title: { [BookModel.Op.like]: 'Beautiful%' } } });
- options: object
public static count()
Count the number of records matching the provided where clause.
See here for details.Parameters:
- options: object
A hash of options to describe the scope of the search.
Return:
Promise<number>
Returns the count of records that match the condition.import BookModel from '../models/BookModel'; // SELECT count(*) AS `count` FROM `book` AS `book`; await BookModel.count();
- options: object
public static update()
Update multiple instances that match the where options.
See here for details.Parameters:
- values: object
hash of values to update. - options: object
update options.
Return:
Promise<Array<number, number>>
The first element is always the number of affected rows, while the second element is the actual affected rows (only supported in postgres withoptions.returning
true).import BookModel from '../models/BookModel'; // UPDATE `book` SET `title`='Lose Yourself' WHERE `id` = 1; await BookModel.update({title: 'Lose Yourself'}, {where: {id: 1}});
- values: object
public static upsert()
Insert or update a single row.
An update will be executed if a row which matches the supplied values on either the primary key or a unique key is found.
Note that the unique index must be defined in your sequelize model and not just in the table.
Otherwise you may experience a unique constraint violation, because sequelize fails to identify the row that should be updated.
See here for details.Parameters:
- values: object
hash of values to upsert. - options: object
upsert options.
Return:
Promise<Model, boolean|null>
returns record and whether row was created or updated as boolean. For Postgres/SQLite dialects boolean value is always null.import BookModel from '../models/BookModel'; // INSERT INTO `book` (`title`) VALUES (?) ON DUPLICATE KEY UPDATE `title`=VALUES(`title`); await BookModel.upsert({title: 'Lose Yourself'});
- values: object
public static destroy()
Delete multiple instances, or set their deletedAt timestamp to the current time if paranoid is enabled.
See here for details.Parameters:
- options: object
destroy options.
Return:
Promise<number>
The number of destroyed rows.import BookModel from '../models/BookModel'; // DELETE FROM `user` WHERE `id` = 1; await BookModel.destroy({where: {id :1}});
- options: object
public static hasOne()
Creates an association between this (the source) and the provided target.
The foreign key is added on the target.
See here for details.Parameters:
- target: Model
The target model. - options: object
hasOne association options.
Return:
sequelize.HasOne
One-to-one association.For one-to-one association we will use two tables as an example they are User and Profile table.
User table has one Profile table and Profile table belongs to the User table.
Here's the relation diagram for it.HasOne put the association key in the target model.
Here User can exist without a Profile, but the vice versa is not possible.
This means, we will insert userId field to Profile model’s table.This is a user model that defines an association in which the user has one profile.
import * as expressExtension from 'express-sweet'; import ProfileModel from './ProfileModel'; export default class extends expressExtension.database.Model { static association() { // User has one profile. this.hasOne(ProfileModel, { foreignKey: 'userId', // profile.userId sourceKey: 'id', // user.id as: 'profile' }); } }
This is an example of record search.
import UserModel from '../models/UserModel'; // SELECT // `user`.`id`, // `user`.`name`, // `profile`.`id` AS `profile.id`, // `profile`.`userId` AS `profile.userId`, // `profile`.`address` AS `profile.address`, // `profile`.`tel` AS `profile.tel` // FROM // `user` AS `user` // LEFT OUTER JOIN `profile` AS `profile` ON `user`.`id` = `profile`.`userId`; // // results in: [ // { // "id": 1, // "name": "Robin", // "profile": { // "userId": 1, // "address": "777 Brockton Avenue, Abington MA 2351", // "tel": "202-555-0105" // } // } // ] await UserModel.findAll({ attributes: ['id', 'name'], include: [{ association: 'profile', attributes: ['userId', 'address', 'tel'] }] });
- target: Model
public static belongsTo()
Creates an association between this (the source) and the provided target.
The foreign key is added on the source.
See here for details.Parameters:
- target: Model
The target model. - options: object
belongsTo association options.
Return:
sequelize.BelongsTo
One-to-one association.For one-to-one association we will use two tables as an example they are User and Profile table.
User table has one Profile table and Profile table belongs to the User table.
Here's the relation diagram for it.BelongsTo put the associations key in the source model.
Here User can exist without a Profile, but the vice versa is not possible.
This means, we will insert userId field to Profile model’s table.This is a profile model that defines an association whose profile belongs to one user.
import * as expressExtension from 'express-sweet'; import UserModel from './UserModel'; export default class extends expressExtension.database.Model { static association() { // Profile belongs to one user. this.belongsTo(UserModel, { foreignKey: 'userId', // profile.userId, targetKey: 'id', // user.id as: 'user' }); } }
This is an example of record search.
import ProfileModel from '../models/ProfileModel'; // SELECT // `profile`.`id`, // `profile`.`userId`, // `profile`.`address`, // `profile`.`tel`, // `user`.`id` AS `user.id`, // `user`.`name` AS `user.name` // FROM // `profile` AS `profile` // INNER JOIN `user` AS `user` ON `profile`.`userId` = `user`.`id`; // // results in: [ // { // "userId": 1, // "address": "777 Brockton Avenue, Abington MA 2351", // "tel": "202-555-0105", // "user": { // "id": 1, // "name": "Robin" // } // } // ] await ProfileModel.findAll({ attributes: ['userId', 'address', 'tel'], include: { association: 'user', required: true, attributes: ['id', 'name'] } });
- target: Model
public static hasMany()
Creates a 1:m association between this (the source) and the provided target.
The foreign key is added on the target.
See here for details.Parameters:
- target: Model
The target model. - options: object
hasMany association options.
Return:
sequelize.HasMany
One-to-many association.For one-to-many association we will use two tables as an example they are User and Comment table.
User table has many Comment table and Comment table belongs to the User table.
Here's the relation diagram for it.The HasMany put the association key in the target model.
Here user and comments share a one to many relationship.
Each user can make multiple comments while each comment is associated with only a single user.This is a user model that defines an association in which the user has many comments.
import * as expressExtension from 'express-sweet'; import CommentModel from './CommentModel'; export default class extends expressExtension.database.Model { static association() { // User has many comments. this.hasMany(CommentModel, { foreignKey: 'userId', // comment.userId sourceKey: 'id', // user.id as: 'comments' }); } }
This is an example of record search.
import UserModel from '../models/UserModel'; // SELECT // `user`.`id`, // `user`.`name`, // `comments`.`id` AS `comments.id`, // `comments`.`userId` AS `comments.userId`, // `comments`.`text` AS `comments.text` // FROM // `user` AS `user` // LEFT OUTER JOIN `comment` AS `comments` ON `user`.`id` = `comments`.`userId`; // // results in: [ // { // "id": 1, // "name": "Robin", // "comments": [ // { // "userId": 1, // "text": "From Robin #1" // }, // { // "userId": 1, // "text": "From Robin #2" // } // ] // } // ] await UserModel.findAll({ attributes: ['id', 'name'], include: { association: 'comments', attributes: ['userId', 'text'] } });
- target: Model
public static belongsToMany()
Create an N:M association with a join table.
Defining through is required.
See here for details.Parameters:
- target: Model
The target model. - options: object
belongsToMany association options.
Return:
sequelize.BelongsToMany
Many-to-many association with a join table.For many-to-many association we will use two tables as an example they are User and Book table.
User is marking down Books that he has read. Each user can mark as many books as they want, creating a many to many association between User and Books.
Books can belong to many users.
Here's the relation diagram for it.The BlongsToMany put the association key in the target model.
Here user and book share a many to many relationship.
Each user can make multiple books, and each book can be associated with multiple users.This is a user model that defines an association where users and books have a many-to-many relationship.
import * as expressExtension from 'express-sweet'; import BookModel from './BookModel'; export default class extends expressExtension.database.Model { static association() { // Users have many books, and books belong to many users. this.belongsToMany(BookModel, { foreignKey: 'userId', // book.userId sourceKey: 'id', // user.id as: 'books' }); } }
This is an example of record search.
import UserModel from '../models/UserModel'; // SELECT // `user`.`id`, // `user`.`name`, // `books`.`id` AS `books.id`, // `books`.`userId` AS `books.userId`, // `books`.`title` AS `books.title` // FROM // `user` AS `user` // LEFT OUTER JOIN `book` AS `books` ON `user`.`id` = `books`.`userId`; // // results in: [ // { // "id": 1, // "name": "Robin", // "books": [ // { // "userId": 1, // "title": "Beautiful" // }, // { // "userId": 1, // "title": "Lose Yourself" // } // ] // } // ] await UserModel.findAll({ attributes: ['id', 'name'], include: { association: 'books', attributes: ['userId', 'title'] } });
- target: Model
public static query()
Raw Queries.
As there are often use cases in which it is just easier to execute raw / already prepared SQL queries, you can use the Model.query method.
This is an alias for thesequelize.Sequelize.query()
method.
See here for details.Parameters:
- sql: string
SQL string. - options: object
Query options.
Return:
Promise<any>
By default, the function will return two arguments: an array of results, and a metadata object, containing number of affected rows etc. If you are running a type of query where you don't need the metadata, for example a SELECT query, you can pass in a query type to make sequelize format the results.// By default the function will return two arguments - a results array, and an object containing metadata (such as amount of affected rows, etc). // Note that since this is a raw query, the metadata are dialect specific. const [results, metadata] = await BookModel.query("UPDATE book SET title = 'Lose Yourself' WHERE id = 1"); // In cases where you don't need to access the metadata you can pass in a query type to tell sequelize how to format the results. For example, for a simple select query you could do: // We didn't need to destructure the result here - the results were returned directly await BookModel.query("SELECT * FROM book", {type: BookModel.QueryTypes.SELECT});
- sql: string
Authentication
EXPRESS SWEET has built-in passport authentication middleware for user authentication by username and password. You can immediately start user authentication in your application using the authentication configuration file and the authentication service module.
For more information about passport
, click here.
NOTE: If an unauthenticated user makes a request to a URL that allows access only if authenticated, the user will be redirected to the page specified by failure_redirect
.
If that access is asynchronous, a 401
error is returned.
Configuration
The user authentication configuration is defined in the config/authentication.js
file.
Click here to download a sample ESM configuration and here to download a sample CJS configuration.
enabled: boolean
Set to true to enable user authentication using Passport middleware.
User authentication is enabled (true
) by default.session_store: 'memory'|'redis'
The session store instance, defaults to a new MemoryStore(memory
) instance.cookie_name?: string|undefined
The name of the session ID cookie to set in the response (and read from in the request).
The default value isconnect.sid
.cookie_secure?: boolean|undefined
Specifies the boolean value for theSecure
Set-Cookie attribute.
The default istrue
, which sets theSecure
attribute on the cookie.cookie_httpOnly?: boolean|undefined
Specifies the boolean value for the `