Awesome
fluxify
a neat little library powered by bun for building apis.
learning by doing
api reference
- lib/auth
- lib/config
- lib/core
- lib/database
- lib/docs
- lib/event
- lib/exception
- lib/fake
- lib/logger
- lib/repository
- lib/router
- lib/validation
learning by doing section
create a new fluxify project
bun create simylein/fluxify
Please take a look at the little todo app example in the src directory for some basic examples on usage. If you would like to start fresh delete everything in the src
directory and make sure to keep the lib
one which is the library. Then create a main.ts
file inside the src
directory which will become your entrypoint.
scripts
bun fmt
formats your code using prettierbun lint
lints your code using eslintbun check
type checks your code using tscbun bundle
bundles your code for portabilitybun test:dev
runs your tests with hot reloadbun test:prod
runs your tests for productionbun start:dev
starts your http server with hot reloadbun start:prod
starts your http server for productionbun schema:init
initializes an new empty databasebun schema:drop
drops all registered tablesbun schema:sync
syncs all registered tablesbun schema:seed
runs all registered seeds
config
configuration is done by environment variables in the .env
file.
you can find an example in the example.env
file.
default values are given below and will be used if none are provided.
PORT=4000
the server will run on this port, the port must not be occupied <br>
NAME=fluxify
the name of you app, put anything you like, this will show in logs
ALLOW_ORIGIN=*
used for cors, put your frontend address there, the star is a wildcard <br>
GLOBAL_PREFIX=
if you would like to prefix all your routes, should start with a slash <br>
DEFAULT_VERSION=0
used when no version was provided in the router nor endpoint, 0 means disabled
JWT_SECRET=random
keep this secret, use a strong random value <br>
JWT_EXPIRY=1600
after that many seconds your token will expire
CACHE_TTL=0
your cache's time to live in seconds, 0 means disabled <br>
CACHE_LIMIT=0
the maximum amount of items in the cache, 0 means disabled
THROTTLE_TTL=0
your throttle's time to live in seconds, 0 means disabled <br>
THROTTLE_LIMIT=0
how many requests a single ip may do in the given ttl, 0 means disabled <br>
THROTTLE_REGROW=0
amount of requests which regrow in the given ttl, 0 means disabled
DATABASE_PATH=:memory:
provide a file path to a database on your system <br>
DATABASE_MODE=readwrite
can either be readwrite or readonly
LOG_LEVEL=info
can be one of trace debug info warn error, trace will log the most info <br>
LOG_REQUESTS=false
set to true for logging incoming requests, useful in development <br>
LOG_RESPONSES=false
set to true for logging outgoing responses, useful in development
bootstrap
to start up a fluxify http server use
main.ts
import { bootstrap } from 'lib/core';
// import any controllers you plan on using
// import './auth/auth.controller.ts';
bootstrap();
bootstrap
returns a FluxifyServer
object which enables you to build custom logging
main.ts
import { bootstrap } from 'lib/core';
const server = bootstrap();
server.logger({
res: ({ id, timestamp, status, time }) => {
console.log('fires on response', { id, timestamp, status, time });
},
info: ({ timestamp, message, context }) => {
console.log('fires on info', { timestamp, message, context });
},
});
you can also register global headers which will be sent in every response
main.ts
import { bootstrap } from 'lib/core';
const server = bootstrap();
server.header({ 'cache-control': 'no-cache' });
for those who do not like json feel free to override request and response serialization
main.ts
import { bootstrap } from 'lib/core';
const server = bootstrap();
server.serialize({ req: (request) => 'hello', res: (body) => 'world' });
entities
every entity has to have an id: primary()
column
user.entity.ts
import { Infer, column, entity, primary } from 'lib/database';
export const userEntity = entity('user', {
id: primary('uuid'),
username: column('varchar').length(16),
password: column('varchar').length(64),
});
export type User = Infer<typeof userEntity>;
services
use the repository to work with entities
auth.service.ts
import { repository } from 'lib/repository';
import { userEntity } from '../user/user.entity';
import { SignUpDto } from './dto/sign-up.dto';
const userRepository = repository(userEntity);
export const signUp = async (body: SignUpDto): Promise<void> => {
await userRepository.insert(body);
};
controllers
register routes
auth.controller.ts
import { router } from 'lib/router';
const app = router();
app.get('/hello', null, () => {
return 'hello world'; // will become the response body
return new Response(); // you can also directly control it
});
validate request bodies
sign-up.dto.ts
import { Infer, object, string } from 'lib/validation';
export const signUpDto = object({
username: string().max(16),
password: string().max(64),
});
export type SignUpDto = Infer<typeof signUpDto>;
auth.controller.ts
import { router } from 'lib/router';
import { signUp } from './auth.service';
import { signUpDto } from './dto/sign-up.dto';
const app = router('/auth');
app.post('/sign-up', { body: signUpDto }, ({ body }) => {
return signUp(body); // enjoy the validated request body
});
validate query params
user-query.dto.ts
import { Infer, object, string } from 'lib/validation';
export const userQueryDto = object({
username: string().optional().max(16),
});
export type UserQueryDto = Infer<typeof userQueryDto>;
user.controller.ts
import { router } from 'lib/router';
import { userQueryDto } from './dto/user-query.dto';
import { findUsers } from './user.service';
const app = router('/user');
app.get('', { query: userQueryDto }, ({ query }) => {
return findUsers(query); // enjoy the validated query params
});
authentication
require auth by adding the jwt: jwtDto
in the schema
import { jwtDto } from 'lib/auth';
import { router } from 'lib/router';
import { findMe } from './auth.service';
const app = router('/auth');
app.get('/me', { jwt: jwtDto }, ({ jwt }) => {
return findMe(jwt.id);
});
logging
use the built in logger which also calls your custom logger
any-service.service.ts
import { debug, info, warn } from 'lib/logger';
debug('print a debug message');
info('print a info message');
warn('print a warn message');
seeding
you can register seeds by naming a file anything.seed.ts
user.seed.ts
import { word, words } from 'lib/fake';
import { repository } from 'lib/repository';
import { userEntity } from '../user/user.entity';
const userRepository = repository(userEntity);
export const users = async (): Promise<void> => {
await Promise.all(
[
...new Set(
Array(8)
.fill(null)
.map(() => word(4)),
),
].map((username) => userRepository.insert({ username, password: words(4).split(' ').join('-') })),
);
};
documentation
you can return the openapi 3 standard in json
docs.controller.ts
import { generateDocs } from 'lib/docs';
import { router } from 'lib/router';
const app = router();
app.get('/docs', null, () => {
return generateDocs();
});
api reference section
auth
hash()
useful for hashing passwords. uses createHash
from crypto under the hood
signJwt()
signs json web tokens using the sha256 algorithm. depends on the configured jwtSecret
and jwtExpiry
from config. uses createHmac
from crypto under the hood
verifyJwt()
verifies json web tokens using the sha256 algorithm. depends on the configured jwtSecret
and jwtExpiry
from config. uses createHmac
from crypto under the hood
jwtDto
useful for marking a route with authentication. also performs validation to ensure proper content in jwt payload
config
config
holds the global configuration state of the application. change properties only if you know what you are doing
core
bootstrap()
used for bootstrapping the application
FluxifyServer
the returned type from bootstrap
database
entity()
create entities by providing an table name and some columns. every entity needs at least an id: primary()
column. pass the return to the repository
for easy data manipulation
primary()
this marks your id column as the primary key. uses non nullable uuids version 4 or auto incrementing integers
column()
create database columns inside the entity
function
relation()
this marks a column as a foreign key. provide the entity you would like to relate against
created()
creates a column which holds the date of creation of said row
updated()
creates a column which holds the date of last update to said row
deleted()
creates a column which holds the date on which the entity was soft deleted
runQuery()
selectMany()
selectOne()
insertOne()
insertMany()
raw methods in which you may write your sql directly. only use them if you cannot fulfill you needs using the repository
Entity
the return type of the entity
function
Infer
provide any entity as the type argument for getting a inferred type which is bound to that entity. works like magic
docs
generateDocs()
returns a openapi v3 compliant spec of your api routes. return this from any route handler
event
subscribe()
used for server sent events. return this from a get route handler and provide a channel for listening
emit()
emits an event with some optional data to the specified channel
exception
Accepted() NoContent() BadRequest() Unauthorized() Forbidden() NotFound() MethodNotAllowed() Conflict() Gone() IamTeapot() Locked() TooManyRequests() InternalServerError()
throw those for returning the corresponding http status codes. they accept an optional message which will override the default one
fake
adjective() adjectives()
returns one or many random adjectives
color() colors()
returns one or many random colors
element() elements()
returns one or many random periodic elements
email() emails()
returns one or many random email addresses
excuse() excuses()
returns one or many random developer excuses
material() materials()
returns one or many random materials
product() products()
returns one or many random products
quote() quotes()
returns one or many random famous quotes
sentence() sentences()
returns one or many random conversational sentences
username() usernames()
returns one or many random usernames
logger
trace() debug() info() warn() error()
they all call their console equivalent function internally. also call custom logger functions registered on bootstrap. they only fire if the log level matches or is higher than the specific function
repository
repository()
provide an entity and get access to rich data manipulation. use functions like find()
insert()
update()
and delete()
to work with the chosen entity
IdEntity
the most basic entity possible. all entities must correspond to this type
FindOptions FindOneOptions
the types used in find
and findOne
from repository. they require generics to work
InsertData
all keys of an entity which need to be present at insert. requires generics to work
UpdateData
all keys of an entity which need to be present at update. requires generics to work
router
router()
returns access to get
post
put
patch
delete
and all
route handlers. accepts an optional prefix which will affect all routes. you can also override the global prefix or version to be used by its routes
get() post() put() patch() delete() all()
route handlers used to map http routes in your application. they accept an endpoint and optional schema for validation and a handler which will be called when a request hits said endpoint
validation
string() number() boolean() object() uuid() date() union() array() blob()
they all validate types on the runtime while providing awesome intellisense in development
Infer
provide any dto as the type argument for getting a inferred type which is bound to that dto. works like magic