curie-server
v2.4.5
Published
A modular Node.js http/1.0 framework
Downloads
3
Readme
Curie
A modular Node.js http/1.0 framework
Table of Content
Config
Curie-server accepts a number of configuration options of the type:
interface ServerParams {
routes: string
routeParser: ClassConstructor<RouteParser>
public: string
port: number
listeners: [string, string | RegExp]
middleware: [string, string | RegExp]
database: string
preRun: string[]
root: string
}
Keep in mind that all the paths are relative to the main file
File structure
curie-server supports a multi file structure. An example file structure:
|public/
|--css/
|----main.css
|--js/
|----main.js
|routes/
|--index.pug
|listeners/
|--index.list.ts
|middleware/
|--logger.mdw.ts
|index.ts
|database.ts
Main file
The main file is the file, which is responsible for starting your application.
import { initApp } from "curie-server"
initApp({
port: 8000,
public: "../public",
routes: "../routes",
listeners: ["./listeners", "list.[jt]s"],
middleware: ["./middleware", "mdw.[jt]s"],
database: ""
})
Database file
The database file is responcible for connecting with your database. It should export a class which extends the DBridge class. You may create your own class or use the so-called out of the box PotgreSQL DBridge. It should look something like it:
import { PostDBridge, database } from "curie-server"
@database("postgres://postgres:[email protected]:5432/postgres")
export default class extends PostDBridge {}
Listeners
Listeners are responsible for responding to incoming http requests. Both their location and extension are specified in the Server
constructor parameters in the main file. A listener should extend the Listener class, implement onGET
and/or onPOST
method(s), such that they return CallbackReturnType. Example:
import c, { Listener, hookup } from "curie-server";
@hookup("/")
export default class extends Listener {
async onGET(req: c.Request, res: c.Response): c.CallbackReturnType {
this.server.routeParser.render(res, "index")
return [null, false]
}
}
Middleware
Middleware is responcible for interrupting incoming requests and can even reject them. Middleware should extends the Middleware class and return the CallbackReturnType. It should look something like this:
import { CallbackReturnType, Middleware, withTime, Request, Response, c_log, use } from "curie-server";
@use()
export default class extends Middleware {
async intercept(req: Request, res: Response) {
c_log(withTime(`[LOGGER]> ${req.method}: ${req.url || ""}`))
return [null, true] as CallbackReturnType
}
}
RouteParser
The RouteParser
is responsible for parsing and rendering template files. If you want to use the lamplating language of your choice, you should provide a class that extends the RouteParser
.
abstract class RouteParser<RouteType = any> {
path: string
routes: LooseObject<RouteType>
server: Server
public constructor(path: string, server: Server) {/*...*/}
abstract compile(route: string): CallbackReturnType
abstract async compileAll(): Promise<CallbackReturnType>
abstract async render(res: Response, route: string, locals?: LooseObject): Promise<CallbackReturnType>
}
Out of the box curie-server is providing support for the pug templating lang.
Routes
Routes are themplates rendered by the RouteParser. Out of the box you get the PugParser
, which compiles .pug
files and allows you to query items from the database
(template: //# $<variable_name>: <query>
).
//# $posts: SELECT * FROM posts
<!DOCTYPE html>
html(lang="en")
head
// ...
body
ul.posts
for post in posts
li.posts__post
h2.posts__post__title= post.title
p.posts__post__body= post.body
Single file approach
While I highly advise you to take the advatnage of the multi file structure, around which the curie-server
was built, you can fit everything into a single file.
import c, { Server, PostDBridge, Listener, Middleware, c_log, withTime, initApp, database, hookup, use } from "curie-server";
(async () => {
await initApp({
port: 8000,
public: "../public",
routes: "../routes",
listeners: ["./listeners", "list.[jt]s"],
middleware: ["./middleware", "mdw.[jt]s"],
database: ""
})
@database("postgres://postgres:[email protected]:5432/postgres")
class Db extends PostDBridge {}
@hookup("/")
class IndexListener extends Listener {
async onGET(req: c.Request, res: c.Response) {
await this.render(res, "index")
return [null, false] as c.CallbackReturnType
}
}
@use()
class Logger extends Middleware {
async intercept(req: Request, res: Response) {
c_log(withTime(`[LOGGER]> ${req.method}: ${req.url || ""}`))
return [null, true] as c.CallbackReturnType
}
}
})()
Classes
DBridge
@database("<connection_uri>")
class MyDBridge extends DBridge<DBInterface, QueryType> {
async get(query: QueryType): Promise<LooseObject[] | any> {
// Fetch the query! Good boy!
return getResponse
}
async update(query: QueryType): Promise<UpdateResponse | any> {
// Update something...
return updateResponse
}
async delete(query: QueryType): Promise<DeleteResponse | any> {
// Delete something...
return deletionResponse
}
async create<T extends ClassConstructor>(model: T, data: ConstructorParameters<T>): Promise<CreateResponse | any> {
// Create something...
return creationResponse
}
}
Listener
@hookup("/")
class IndexListener extends Listener {
async onGET(
req?: Request,
res?: Response
): Promise<CallbackReturnType | undefined> {
return [null, true]
}
async onPOST(
req?: Request,
res?: Response
): Promise<CallbackReturnType | undefined> {
return [null, true]
}
}
Middleware
@use()
export default class Intercepter extends Middleware {
async intercept(req?: Request, res?: Response) {
// Do something
return [null, true] as CallbackReturnType
}
}
Interfaces and types
LooseObject
An implementation of a key-value map.
interface LooseObject<T = any> {
[key: string]: T
}
Request
interface Request extends http.IncomingMessage {
query: LooseObject<string>
cookies: cookies
body: LooseObject
}
Response
interface Response extends http.ServerResponse {
cookies: cookies
}
ServerParams
It is a configuration object passed to the Server constructor.
interface ServerParams {
routes: string,
routeParser: ClassConstructor<RouteParser>
public: string
port: number
listeners: [string, string | RegExp] // [path_to_dir, extension]
middleware: [string, string | RegExp] // [path_to_dir, extension]
database: string
root: string
preRun: string[]
[key: string]: any | any[]
}
Server.DEFAULT_SERVER_OPTIONS: ServerParams = {
routes: "./routes",
routeParser: PugParser,
public: "./public",
port: 8000,
listeners: ["./", "list.[jt]s"],
middleware: ["./", "mdw.[jt]s"],
database: '',
preRun: [],
root: path.dirname(require.main.filename)
}
CallbackReturnType
CallbackReturnType is a value returned by many curie-server
functions, but is used especially in the Listener and the Middleware classes. The first part of the tuple is the Error
, and the 2nd one is the ShouldContinue
boolean, which tells the inner loop whether is should send the Response to the client or continue.
type CallbackReturnType = [Error | null, boolean]
Helpers
Logging
curie-server
comes with a plethora of tools to help you log info.
const log = (text: any, color: keyof ChalkColors) =>
console.log(
((chalk[color] as any) as Executable<string>)(
typeof text === "object" ? JSON.stringify(text) : text
)
)
const c_log = (text: string) => log(text, "yellowBright")
const initLogger =
(name: string, color: keyof ChalkColors): LoggerFunction
=> (text: string)
=> log(withTime(`[${name}]> ${text}`), color)
PreRun commands
initApp accepts a parameter called preRun
. It's simply an array of commands to run before the rest of initialization (loading files, setting up events etc.). Errors won't terminate the main process, but only propagate the error message to the console (same with stdout).
initApp({
// ...
preRun: [
"tsc"
]
})
In the example, the tsc
command will run before loading files to a memory, thus allowing you to compile i.e. typescript files.
CLI
curie-server
listens for a console input and evaluates it upon pressing Enter
. Errors won't terminate the main process. this
refers to the Server
instance.