Skip to content

Commit

Permalink
feat(roter): add Router class implementation
Browse files Browse the repository at this point in the history
- add router class with necessary methods
- add example folders to show Router usage
- refactor and fixes
  • Loading branch information
ajimae committed Jul 24, 2022
1 parent 9851bed commit b6487df
Show file tree
Hide file tree
Showing 28 changed files with 616 additions and 52 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,4 @@ dist
# custom
generate.sh
*.http
example*
example
88 changes: 84 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Fast, lightweight and zero dependency framework for [bunjs](https://bun.sh) 🚀

![npm](https://img.shields.io/npm/v/colstonjs?color=blue&style=plastic)
![GitHub Workflow Status](https://img.shields.io/github/workflow/status/ajimae/colstonjs/publish?style=plastic) ![npm](https://img.shields.io/npm/v/colstonjs?color=blue&style=plastic)
![GitHub](https://img.shields.io/github/license/ajimae/colstonjs?style=plastic)
![npm](https://img.shields.io/npm/dt/colstonjs?style=plastic)

Expand All @@ -13,21 +13,26 @@ Fast, lightweight and zero dependency framework for [bunjs](https://bun.sh) 🚀
- [Install](#installation)
- [Usage](#usage)
- [Examples](#examples)
- [Hellow Bun](#hello-bun)
- [Hello Bun](#hello-bun)
- [Read request body as json or text](#read-request-body-as-json-or-text)
- [Using named parameters](#using-named-parameters)
- [Using query parameters](#using-query-parameters)
- [Method Chaining](#method-chaining)
- [Running the demo note-app](#running-the-demo-note-app)
- [Middleware](#middleware)
- [Application-Level Middleware](#application-level-middleware)
- [Route-Level Middleware](#route-level-middleware)
- [Context `locals`](#context-locals)
- [Router](#router)
- [Instantiating Router class](#instantiating-router-class)
- [Injecting Router instance into the app](#injecting-router-instance-into-the-app)
- [Application instance cache](#application-instance-cache)
- [Error Handler](#error-handler)
- [Benchmark](#benchmark)
- [Contribute](#contribute)
- [License](#license)
- [Author](#author)
- [Note](#note:)
- [DevNote](#devnote)

## Background

Expand Down Expand Up @@ -198,6 +203,17 @@ app
app.start(8000);
```
#### Running the demo `note-app`
Follow the steps below to run the `demo note-taking api application` in the `examples`directory.
- Clone this repository
- Change directory into the note-app folder by running `cd examples/note-app`
- Start the http server to listen on port `8000` by running `bun app.js`
- User your favourite `http client` (e.g Postman) to make requests to the `listening http server`.
<a href="https://raw.githubusercontent.com/ajimae/colstonjs/master/postman_collection.json" download>
<img src="postman.jpeg" width="120">
</a>
### Middleware
Colstonjs support both `route` level middleware as well as `app` level middleware.
Expand Down Expand Up @@ -267,7 +283,71 @@ app.get("/", middleware-1, middleware-2, middleware-3, ..., middleware-k, (ctx:
});
...
```
#### Context locals
`ctx.locals` is a plain javascript object that is specifically added to allow sharing of data amongst the chain of middlewares and/or handler functions.
```ts
// server.ts
...
let requestCount = 0;
app.post("/request-count", (ctx, next) => {
/**
* req.locals can be used to pass
* data from one middleware to another
*/
ctx.locals.requestCount = requestCount;
next();
}, (ctx, next) => {
++ctx.locals.requestCount;
next();
}, (ctx) => {
let count = ctx.locals.requestCount;
return ctx.status(200).text(count); // 1
});
```
### Router
#### Instantiating Router class
Router class provide a way to separate router specific declaration/blocks from the app logic, by providing that extra abstraction layer for your project.
```typescript
// router.ts
import Router from "Router";

// instantiate the router class
const router1 = new Router();
const router2 = new Router();

// define user routes - can be in a separate file or module.
router1.post('/user', (ctx) => { return ctx.status(200).json({ user }) });
router1.get('/users', (ctx) => { return ctx.json({ users }) });
router1.delete('/user?id', (ctx) => { return ctx.status(204).head() });

// define the notes route - can also be in separate module.
router2.get('/note/:id', (ctx) => { return ctx.json({ note }) });
router2.get('/notes', (ctx) => { return ctx.json({ notes }) });
router2.post('/note', (ctx) => { return ctx.status(201).json({ note }) });

export { router1, router2 };
```
#### Injecting Router instance into the app
```typescript
// server.ts
import Colston from "colstonjs";
import { router1, router2 } from "./router";

const app: Colston = new Colston();

app.all(router1, router2);

// other routes can still be defined here
app.get("/", (ctx) => {
return ctx.status(200).text("Welcome to colstonjs framework for bun");
});

app.start(8000)
```
The `app.all()` method takes in k numbers of router instance objects e.g `app.all(router-1, router-2, ..., router-k);`. The [example](example) folder contains a full note taking backend app that utilizes this pattern.
## Application instance cache
We can cache simple data which will leave throughout the application instance lifecycle.
Expand Down Expand Up @@ -445,5 +525,5 @@ See the TODO doc [here](todo.md), feel free to also add to the list by editing t
## Author
Coded with 💙 by [Chukwuemeka Ajima](https://github.com/ajimae)
## Note:
## DevNote:
Although this version is fairly stable, it is actively still under development so also is [bunjs](https://bun.sh) and might contain some bugs, hence, not ideal for a production app.
7 changes: 1 addition & 6 deletions clean-dist
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,7 @@ cat << EOF > dist/index.d.ts
import Colston from "./declarations/colston";
export default Colston;
export * from "./declarations/types.d";
EOF

# update imports in index.js
cat << EOF > dist/index.js
import Colston from "./src/colston";
export default Colston;
export { default as Router } from "./src/router";
EOF

echo "✨ Done"
63 changes: 63 additions & 0 deletions examples/note-app/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Colston from "../../index";
import router from "./routes";
import { logger, requestID } from "./middleware";

const app = new Colston();

let requestCount = 0;

app
.get("/one", (ctx) => {
requestCount++;
return ctx.status(200).text("One");
})
.get("/two", ctx => ctx.text("two"))
.post("/two", (ctx) => {
requestCount++;
return ctx.status(200).text("Two");
})
.patch("/three", (ctx) => {
requestCount++;
return ctx.status(200).text("Three");
});

app.post("/requestCount", (ctx, next) => {
/**
* req.locals can be used to pass
* data from one middleware to another
*/
ctx.locals.requestCount = requestCount;
next();
}, (ctx, next) => {
++ctx.locals.requestCount;
next();
}, (ctx) => {
return ctx.status(200).text(ctx.locals.requestCount.toString());
});

app.get("/request-id", requestID, (ctx) => {
return ctx.status(200).json({
message: "This will give every request a unique ID and in the header too.",
requestID: ctx.request.id
});
});

/**
* the app.all(...route: Router) mehtod
* accepts k-numbers of router instance objects
* where each router instance object are
* @example
*
* router-1 = new Router().get(path, ...middlewares)
* router-2 = new Router().post(path, ...niddlewares)
* ...
* router-k = new Router().<method>(path, ...middlewares)
*
* app.all(router-1, router-2, ..., router-k)
*/
app.use(logger);
app.all(router);

app.start(8000, function () {
console.log(`server running on port {8000}`);
});
65 changes: 65 additions & 0 deletions examples/note-app/controller/NoteController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/**
* NoteController class
* @class NoteController
* @methods getNotes
* @methods postNotes
*/
class NoteController {
constructor(noteRepository) {
this.noteRepository = noteRepository;
}

/**
* retrieve all available notes
* @param {*} ctx
* @returns bun Response instance
*/
async getNotes(ctx) {
const notes = await this.noteRepository.getNotes(ctx);

return ctx.status(200).json({
success: true,
data: notes
});
}

/**
* retrieve all available notes
* @param {*} ctx
* @returns bun Response instance
*/
async getAllNotes(ctx) {
const notes = await this.noteRepository.getAllNotes();

return ctx.status(200).json({
success: true,
data: notes
});
}

/**
* post a single note data
* @param {*} ctx
* @returns bun Response instance
*/
async postNote(ctx) {
const notes = await this.noteRepository.postNote(ctx);

return ctx.status(201).json({
success: true,
data: notes
});
}

/**
* delte a sinlge note data
* @param {*} ctx
* @returns bun Response instance
*/
async deleteNote(ctx) {
await this.noteRepository.deleteNote(ctx);
return ctx.status(204).head();
}
}

export default NoteController
2 changes: 2 additions & 0 deletions examples/note-app/controller/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import NoteController from "./NoteController";
export default NoteController;
20 changes: 20 additions & 0 deletions examples/note-app/dataStore/data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
[
{
"id": "L4JPI2tLmXMpwmaPcUZZlfuLljW7JYPP2hfmYcmSQ",
"note": "this is my first note",
"createdAt": "1970-01-01T00:00:00.005Z",
"updatedAt": "1970-01-01T00:00:00.005Z"
},
{
"id": "jyBuT1zpMLc0uIQGDOKNkJJmI9YCzg5LLWU2OgkgE",
"note": "this is my second note",
"createdAt": "1980-01-01T00:00:00.035Z",
"updatedAt": "1980-01-01T00:00:00.035Z"
},
{
"id": "QbSMatSi9YVxKQBQny9yBfAh8E3VWkkWg8GMHwQHdOA",
"note": "this is my third note",
"createdAt": "1990-01-01T00:00:02.000Z",
"updatedAt": "1990-01-01T00:00:02.000Z"
}
]
2 changes: 2 additions & 0 deletions examples/note-app/middleware/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { logger } from "./logger";
export { requestID } from './requestID';
4 changes: 4 additions & 0 deletions examples/note-app/middleware/logger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const logger = (ctx) => {
const { pathname } = new URL(ctx.request.url);
console.info("- - " + [new Date()], "- - " + ctx.request.method + " " + pathname + " HTTP 1.1" + " - ");
}
6 changes: 6 additions & 0 deletions examples/note-app/middleware/requestID.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import crypto from "crypto"; // built into bun

export const requestID = (ctx) => {
ctx.request.id = crypto.randomBytes(18).toString('hex');
ctx.setHeader('request-id', ctx.request.id);
}
1 change: 1 addition & 0 deletions examples/note-app/models/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./notes";
32 changes: 32 additions & 0 deletions examples/note-app/models/notes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import crypto from 'crypto';
const data = require('../dataStore/data.json')

export function find(id) {
return data.find((v) => v.id == id);
}

export function findAll() {
return data;
}

export function save(datum) {
const id = crypto.
randomBytes(32)
.toString('base64')
.replace(/[_+=\/]/gi, '');

const _data = {
id,
...datum,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
}

data.push(_data);
return _data;
}

export function Delete(id) {
const idx = data.findIndex(v => v.id == id);
return data.splice(idx, 1);
}
Loading

0 comments on commit b6487df

Please sign in to comment.