Stampede
🦕 Deno REST framework/eco-system
Install / Use
/learn @bashovski/StampedeREADME
Stampede 🦕
- To see the source code of Stampede CLI, check https://github.com/bashovski/stampede-cli
About
- Stampede is a framework, or an eco-system written in TypeScript for Deno, made with emphasis on delivering new features quicker than ever before.
Features
- CLI - generate modules instantly and make progress without fuss!
- Convenient M(V)CS structure - strong emphasis on separation of concerns
- Autowired modules and loaders - implicitly run codebase without piling up large imports
- Authentication system ready for use
- Modified Koa Router utilization - easy-to-use Routing, HTTP request handlers, etc.
- Deployment configuration which is production-ready
- View layer written in Vue.js - includes auth pages, guards, etc.
- DenoDB ORM that supports PostgreSQL
- Pre-written migrations with Umzug and Sequelize (there's raw SQL as well)
- Feature Flags - toggle what features should be running during server runtime (configurable even during runtime)
- Autowired config - adding and consuming environment variables kept elegant
- Multi-level logger with DataDog support (allows usage of facets and tags)
- Custom Mail module/internal micro-library compatible with SendGrid
- Validators - validate and restrict invalid user input with large variety of options
- Factories - populate your database with false records (powered by faker.js)
- Route Guards for your Vue SPA - modify the behavior of page access layer the way you want to
- Autowired CRON jobs
- Unit tests with bootstrappers
- Insomnia and Swagger doc for existing REST API endpoints
- and many more...
Requirements
| Service | Min. version | |------------|--------------| | Deno | 1.3.0 | | V8 | 8.6.334 | | TypeScript | 3.9.7 | | Node¹ | 12.6.3 | | NPM¹ | 6.14.4 |
¹ - Not mandatory, used only for migrations with Umzug.
Installation
- Clone the repository:<br/>
git clone https://github.com/bashovski/stampede
- Cd into the project directory:<br/>
cd stampede
- Create
.envfile using.env.exampleas a base:<br/>
cp .env.example .env
- Update environment variables in .env file. (DB_*) fields are mandatory.<br/> You can, but you don't have to set up local PostgreSQL Docker container for dev purposes as it's very easy to use by running:
docker run --name stampede-project-db -e POSTGRES_PASSWORD=your_db_password -d -p 5432:5432 postgres
If you already have Postgres service up and running, you can avoid the command above.<br/>
- In order for programmatic migrations to be ran, please install Node dependencies:
npm i - Once they are installed, execute the run command to start up the server:
./scripts/run
- Your server will be up and running now.
- After booting the server, you can serve UI written in Vue.js:
- Open a separate terminal window/tab and run the command from root project directory:
cd ui && npm i
npm run serve
You can normally run the UI serve script once you have dependencies installed for it:
./scripts/ui # From root project directory
-
By default, the server will be accessible at:
http://localhost:80/, <br/> and the UI will be accessible fromhttp://localhost:8080 -
Now, as you have both the server and client up and running, feel free to read how Stampede works and check out some of the tutorials and blogs!
Models
-
Each table in a database has a corresponding model.
-
Using DenoDB ORM, you may apply mutations to the model's instances, persist new records, or remove them from the database.
-
We can view models as blueprints for certain objects we want to store and consume in our application.
-
Using Stampede CLI, you can easily create new model:
stampede model Video- creates Video.ts model inmodels/directory. -
Let's take a look at an example of a well-structured model:
import { DataTypes } from 'https://raw.githubusercontent.com/eveningkid/denodb/abed3063dd92436ceb4f124227daee5ee6604b2d/mod.ts';
import Model from '../lib/Model.ts';
class Video extends Model {
static table = 'videos'; // Name of the table in DB
static timestamps = true; // Enables created_at and updated_at columns
// We'll keep it simple, here are a couple of fields related to the model:
// For more info regarding usage of DenoDB ORM: https://eveningkid.github.io/denodb-docs/
static fields = {
id: {
type: DataTypes.UUID,
primaryKey: true
},
title: {
type: DataTypes.STRING,
length: 256,
allowNull: false
},
channelId: {
type: DataTypes.UUID,
allowNull: false
},
views: {
type: DataTypes.INTEGER,
allowNull: false
}
};
}
export default Video;
- That'd be a very basic model example. In order for the model metadata to be appended to a database, you'll need to create a migration, and assure each field is present in the migration. Please read the 'Migrations' section below.
Routing
- Once you create a model using CLI, you also create a routing for that model. Routings can always be independent of the model, and therefore
do not need to have any association with any model whatsoever. It is advisable to follow the Pascal-case naming convention of modules - e.g.
ModelRouting.ts.
As we mentioned earlier, Stampede utilizes Koa router which offers many functionalities and in this case, we'll explain proper usage of routes. Let's use a small chunk of UserRouting.ts:
/*
* To follow the convention, HTTP requests are being handled by controllers. That's why we are using UserController here.
* UserController will consume the context argument - passed required headers and sent data to a service (in this case UserService).
* UserService will process passed data, perhaps update the database and return the response (ServiceResult).
* Once controller receives response (ServiceResult), it will most likely send the response to the client.
*/
import Router from '../lib/Router.ts';
import UserController from '../controllers/UserController.ts';
import AuthMiddleware from '../middleware/AuthMiddleware.ts';
import HttpResponse from '../http/HttpResponse.ts'; // Only used for last example
/**
* Now, let's take a look at the login request:
* Notice how we only require the Router.post function to invoke UserController.loginUser and nothing else.
* The loginUser function will handle the sending of response and controller -> service transaction prior to that.
*/
Router.post('/users/login', UserController.loginUser);
/**
* In this example, we want to fetch current user's information - such as username, date of birth, avatar, etc.
* In order to achieve that, we need to primarily assure that the client which sends the request is authenticated user.
* Now notice that we are using AuthMiddleware.authenticateUser method before retrieving user's info (IAM).
* In this case, AuthMiddleware.authenticateUser behaves as a middleware and checks if there's a session with passed session cookie.
* Obviously, if the cookie isn't passed or if the session with corresponding cookie has expired, the authentication will fail,
* therefore the user won't be allowed to retrieve their data since service won't be able to identify the user with no linked session.
*/
Router.get('/users/iam', AuthMiddleware.authenticateUser, UserController.IAM);
/**
* Let's take a look at another example, which is pretty much straight-forward:
* Note that the controller functions are usually like the arrow function below.
* They obviously consume the ctx argument from which many things can be derived,
* including headers, query params, request body, cookies, etc.
*
* In the example below we are creating an instance of HttpResponse and passing two arguments:
* first arg: status code
* second arg: body of the response
*
* We use the HttpResponse's send() method which consumes response from context and that's it!
*/
Router.get('/users/count', (ctx: any) => {
new HttpResponse(200, {
message: 1000 // trivial
}).send(ctx.response);
});
Controllers
-
Controllers are responsible for handling HTTP requests and sending adequate responses derived from services to a client.
-
We differentiate two types of responses returned by services: a HttpResponse and a HttpError.
-
Both of them are very similar and in fact, HttpError as a class extends HttpResponse.
-
The emphasis here is on semantics, therefore instance of HttpError class may only have status code which is identified as error status code (400-599).
-
To generate a controller, you can use Stampede CLI:
stampede ctrl Profile- creates ProfileController.ts -
You may also provide more controller names as command arguments simultaneously:
stampede ctrl Profile Story Post Follower -
Let's examine an example of a controller:
import Controller from './Controller.ts';
import PostService from '../services/PostService.ts';
import Logger from '../lib/Logger.ts';
/**
* @class PostController
* @summary Handles all Post-related HTTP requests
*/
class PostController extends Controller {
/**
* @summary Handles index request
* @param ctx
*/
static async index(ctx: any): Promise<void> {
try {
/*
* Notice how we destructure the service result. As
Related Skills
bluebubbles
343.1kUse when you need to send or manage iMessages via BlueBubbles (recommended iMessage integration). Calls go through the generic message tool with channel="bluebubbles".
gh-issues
343.1kFetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
healthcheck
343.1kHost security hardening and risk-tolerance configuration for OpenClaw deployments
node-connect
343.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
