UNPKG

17.6 kBMarkdownView Raw
1<p align="center">
2 <img width="250" src="/docs/huncwot-logo.svg">
3</p>
4
5<h1 align="center">
6 <a href="https://huncwot.org">Huncwot</a>
7</h1>
8
9<p align="center">
10 <b>Macro framework for monolithic JavaScript applications, batteries included</b>
11</p>
12
13<br>
14
15<p align="center">
16 <a href="https://landing.mailerlite.com/webforms/landing/a3k0m1"><img src="https://img.shields.io/badge/%20newsletter%20-%20subscribe%20-blue.svg?style=for-the-badge" alt="Subscribe to Huncwot Newsletter"></a>
17 <a href="https://www.npmjs.com/package/huncwot"><img src="https://img.shields.io/npm/v/huncwot.svg?style=for-the-badge" alt="Huncwot Package on NPM"></a>
18 <a href="https://www.npmjs.com/package/huncwot"><img src="https://img.shields.io/npm/dm/huncwot.svg?style=for-the-badge" alt="Huncwot Package on NPM"></a>
19 <a href="https://discord.gg/fdqx3BD"><img src="https://img.shields.io/badge/Discord-join%20chat-738bd7.svg?style=for-the-badge" Alt="Huncwot channel on Discord"></a>
20</a>
21
22Huncwot is a **macro framework** for **monolithic** web applications built for modern JavaScript with « batteries included » approach. It is an **integrated** solution that optimizes for programmers productivity by reducing choices and incorporating community conventions.
23
24[Website](https://huncwot.org) |
25[Twitter](http://twitter.com/huncwot)
26
27> The documentation is a bit outdated. Check the [Huncwot Example App](https://github.com/zaiste/huncwot-example-app) for inspiration and guidance.
28
29## Table of Contents
30
31* [Features In A Nutshell](#features-in-a-nutshell)
32* [Rationale](#rationale)
33* [Getting Started](#getting-started)
34* [Usage](#usage)
35 * [Server-Side](#server-side)
36 * [Component-based](#component-based)
37* [Concepts](#concepts)
38 * [Database](#databse)
39 * [Handlers](#handlers)
40 * [View](#view)
41 * [Routes](#routes)
42 * [Parameters](#parameters)
43 * [GraphQL](#graphql)
44* [Modules](#modules)
45 * [Auth](#auth)
46 * [Static](#static)
47* [Examples](#examples)
48
49
50## Features In A Nutshell
51
52Huncwot bridges client-side (*frontend*) and server-side (*backend*) development
53by using [a single programming language - JavaScript - across the
54board](https://cdb.reacttraining.com/universal-javascript-4761051b7ae9).
55
56The framework draws inspiration from Rails while trying to be less *magical*, if
57any at all. In Huncwot, you write your applications using
58[TypeScript](https://www.typescriptlang.org/). It also comes with a convenient
59command toolkit (CLI) which wraps over `npm scripts`
60
61As a secondary goal, Huncwot tries to minimize the dependencies. It uses
62external packages only if absolutely necessary (e.g. security, OS abstractions
63etc).
64
65### :gear: Server-side / Backend
66
67* Huncwot is as a replacement for Express & Koa to build server-side applications.
68* Huncwot provides a simpler than Express/Koa, data-driven HTTP handler abstraction.
69* Huncwot comes with a built-in REST endpoint.
70* Huncwot comes with a built-in GraphQL endpoint.
71* Huncwot can collocate [GraphQL](http://graphql.org/) queries with Vue.js components.
72* Huncwot provides a data-driven router
73
74### :bar_chart: Client-side / Frontend
75
76* ... uses [Vue.js](https://vuejs.org/)
77* ... uses [Vuex](https://vuex.vuejs.org/en/) for state management
78* ... uses [Webpack 4](https://webpack.js.org/) for bundling assets
79* ... favors class-style Vue.js components using [vue-class-component](https://github.com/vuejs/vue-class-component)
80* ... can be used as a convenient boilerplate for Vue.js to build client-side applications
81* ... uses [vue-i18n](https://github.com/kazupon/vue-i18n) for internationalization
82
83### :closed_lock_with_key: Security
84
85* Huncwot favors [Argon2](https://en.wikipedia.org/wiki/Argon2) as a hash
86 function for storing passwords over bcrypt and scrypt. bcrypt lacks memory
87 hardness while in scrypt both, memory hardness and iteration count are tied to
88 a single cost factor. On top of that, Argon2 won the Password Hashing
89 Competition in 2015. It is build around AES ciphers, is resistant to ranking
90 tradeoff attacks and more...
91* Huncwot uses the [node-argon2](https://github.com/ranisalt/node-argon2/) package
92
93### :computer: Command Toolkit
94
95### :minidisc: Storage
96
97* Huncwot favors plain (old?) SQL queries over ORMs as [SQL is one of the most valuable skills](http://www.craigkerstiens.com/2019/02/12/sql-most-valuable-skill/)
98* Huncwot provides a SQL-like, data-driven abstractions for the database integration
99* ... uses [Sqorn](https://sqorn.org/) for the database integration which provides a SQL-like abstractions right inside JavaScript
100* ... supports only PostgreSQL
101* ... is unwilling to support NoSQL ([Thank you for your help NoSQL, but we got it from here](https://www.memsql.com/blog/why-nosql-databases-wrong-tool-for-modern-application/))
102
103### :cake: Conventions & Conveniences
104
105* Huncwot enforces the [Folder-By-Feature Directory Structure](#folder-by-feature-directory-structure)
106
107## Rationale
108
109Huncwot is being built with *battery included* approach in mind, i.e. it comes with a (eventually large) library of useful modules which are developped in a coherent way. This stands in direct opposition to Koa approach. Huncwot tries to formalize conventions and eliminate valueless choices by providing solid defaults for building web applications that increase the programmers productivity.
110
111## Getting Started
112
113Install `huncwot` globally to use its CLI commands which simplify frequent operations. You also need to install [yarn](https://yarnpkg.com/en/).
114
115```
116npm install -g huncwot
117```
118
119Generate new application
120
121```
122huncwot new my-project
123cd my-project
124```
125
126Start the application using `huncwot`, `hc` (alias) or `npm`
127
128```
129huncwot start
130```
131
132or
133
134```
135hc start
136```
137
138or
139
140```
141npm start
142```
143
144Visit `https://localhost:8080`
145
146![Huncwot Init](https://raw.githubusercontent.com/zaiste/huncwot/master/docs/huncwot-start.png)
147
148## Features In Detail
149
150### Folder-By-Feature Directory Structure
151
152The directory structure in Huncwot is organized around your application
153**features**, and not **by type**. This means that artifacts, either client-side or
154server-side are kept togheter. In other words, this approach groups together
155entities (classes, functions) that actually work together. This leads to high
156modularity of your application and better cohesion.
157
158The *Folder-By-Feature* approach makes it easier to find files in your
159application directory. It is especially visible once your project grows -
160folder-by-feature is a better long-term approach due its scalability.
161
162To some extend, the *Folder-By-Feature* approach is similar to how recent
163frontend libraries and frameworks (React, Vue, etc) group together HTML,
164JavaScript and Stylesheets. In Huncwot, this simply goes one step further by
165applying a similar technique to the entire application so that it covers both
166frontend and backend.
167
168
169## Usage
170
171Huncwot can be used as a replacement for Express or Koa, but it also goes beyond that by providing opinionated choices to other layers in the stack (view, ORM, etc) required to build a fully functional web application.
172
173There are two essential ways in Huncwot to constract a web application: traditional server-side or modern component-based. Nonenthless, those two approaches can be combined in any proportion.
174
175### Server-side
176
177This is an example of a basic server-side application in Huncwot. Save it to a file e.g. `server.js`, run it with `node server.js` and visit the application `https://localhost:5544`.
178
179> *Note* Don't forget to install `huncwot` by adding it to `package.json` in your project directory followed by `npm install`. If you're starting from scratch, use `npm init` or (better) `huncwot new` described below.
180
181```js
182const Huncwot = require('huncwot');
183const { OK } = require('huncwot/response');
184
185const app = new Huncwot();
186
187// implicit `return` with a `text/plain` response
188app.get('/', _ => 'Hello Huncwot')
189
190// explicit `return` with a 200 response of `application/json` type
191app.get('/json', _ => {
192 return OK({ a: 1, b: 2 });
193})
194
195// set your own headers
196app.get('/headers', _ => {
197 return { body: 'Hello B', statusCode: 201, headers: { 'Authorization': 'PASS' } }
198})
199
200// request body is parsed in `params` by default
201app.post('/bim', request => {
202 return `Hello POST! ${request.params.name}`;
203})
204
205app.listen(5544);
206```
207
208This example shows a regular, server-side application in the style of Express or Koa, e.g. you define various routes as a combination of paths and functions attached to it i.e. route handlers. In contrast to Express, Huncwot handlers only take HTTP `request` as input and always return an HTTP response: either defined explicitly as an object with `body`, `status`, etc keys, or implicitly with an inferred type e.g. `text/plain` or as a wrapping function e.g. `OK()` for `200`, or `created()` for `201`.
209
210
211### Component-based
212
213Component-based means that *pages* are built by combining components: an independant chunks of HTML with their own styling and behaviour defined in JavaScript. There is usually only a single *page* (rendered on the server) to which components are being attached - this happens in the browser (client-side). Routing is usually performed in the browser with paths corresponding to components.
214
215![component](https://raw.githubusercontent.com/zaiste/huncwot/master/docs/component-approach.png)
216
217
218Here's an example of a Vue.js component
219
220```js
221<template>
222 <div class="content">
223 <h2>Counter</h2>
224
225 <div>
226 <span class="counter">{{ $store.state.count }}</span>
227 <span class="notice">(count is: {{ evenOrOdd }})</span>
228 </div>
229
230 <a @click="increment">Increment</a>
231 <a @click="decrement">Decrement</a>
232 <a @click="incrementIfOdd">Increment if odd</a>
233 <a @click="incrementAsync">Increment async</a>
234 </div>
235</template>
236
237<script>
238import { mapGetters, mapActions } from 'vuex'
239
240export default {
241 computed: mapGetters([
242 'evenOrOdd'
243 ]),
244 methods: mapActions([
245 'increment',
246 'decrement',
247 'incrementIfOdd',
248 'incrementAsync'
249 ])
250}
251</script>
252
253<style scoped>
254.counter {
255 font-size: 5rem;
256}
257
258.notice {
259 color: #666;
260 font-weight: 500;
261}
262</style>
263```
264
265## Concepts
266
267### Database
268
269An ORM is at times too much to get data out of the database. Huncwot provides an
270thin layer of integration with various RDMBS systems using
271[Sqorn](https://sqorn.org/). Thanks to this library you can
272write usual SQL queries, yet fully integrated with the regular JavaScript data
273structures.
274
275The database configuration is stored `config/database.json` as a JSON document.
276
277In order to start using the database integration you only need to require `huncwot/db`:
278
279```
280const db = require('huncwot/db');
281```
282
283Let's see how we can perform some basic and frequent SQL queries in Huncwot
284
285#### Select
286
287Get all elements with all columns from `widgets` table; equivalent to `select * from widgets`:
288
289```
290const results = await db`widgets`;
291```
292
293Get all elements with all some columns from `widgets` table; equivalent to `select id, name from widgets`:
294
295```
296const results = await db`widgets`.return('id', 'name');
297```
298
299Get a single element from `widgets` table by `id`:
300
301```
302const result = await db`widgets`.where({ id })
303```
304
305#### Insert/Update
306
307Insert a single element into `widgets` table:
308
309```
310await db`widgets`.insert({ name: 'Widget 1', amount: 2 })
311```
312
313Insert few elements at once into `widgets` table:
314
315```
316await db`widgets`.insert([
317 { name: 'Widget 1', amount: 2 },
318 { name: 'Widget 2', amount: 7 },
319 { name: 'Widget 3', amount: 4 }
320])
321```
322
323Update an existing element (identified by `id`) in `widgets` table:
324
325```
326await db`widgets`.where({ id: 2 }).set({ name: 'Widget 22' })
327```
328
329### Handlers
330
331A handler is a module which groups actions. Actions are functions operating in the context of a single route, i.e. actions defined in `handlers/widgets/` handle the `/widgets` route.
332
333Each action defined in a handler is responsible to connect the information received from the incoming request to underlaying data in your application (i.e. fetching/saving/updating) in order to produce a corresponding view e.g. a HTML page or a JSON payload.
334
335Handlers may define up to five action. Each action is placed in a separate file i.e. `browse`, `read`, `edit`, `add`, `delete` - in short **BREAD** (which is a kind of extension of CRUD approach). Each of those functions is triggered by a corresponding HTTP method i.e. `browse()` and `read()` by `GET`, `edit()` by `PUT`, `add()` by `POST` and finally `destroy()` by `DELETE`.
336
337Here's an example of a handler with five actions defined in `handlers/widgets/` directory.
338
339Inside `handlers/widgets/browse.js`:
340
341```js
342const { OK } = require('huncwot/response');
343
344async function browse(request) {
345 const results = ...
346 return OK(results);
347}
348
349module.exports = browse
350```
351
352Inside `handlers/widgets/read.js`
353
354```js
355const { OK } = require('huncwot/response');
356
357async function read(request) {
358 const { id } = request.params;
359 const result = ...
360 return OK(result);
361}
362
363module.exports = read
364```
365
366Inside `handlers/widgets/edit.js`:
367
368```js
369const { OK } = require('huncwot/response');
370
371async function edit(request) {
372 const { id, name } = request.params;
373 ...
374 return OK({ status: `success: ${id} changed to ${name}` });
375}
376
377module.exports = edit
378```
379
380Inisde `handlers/widgets/add.js`:
381
382```js
383const { created } = require('huncwot/response');
384
385async function add(request) {
386 const { name } = request.params;
387 ...
388 return created({ status: `success: ${name} created` });
389}
390
391module.exports = add
392```
393
394Inside `handlers/widgets/destroy.js`:
395
396```js
397const { OK } = require('huncwot/response');
398
399async function destroy(request) {
400 const { id } = request.params;
401 ...
402 return OK({ status: `success: ${id} destroyed` });
403}
404
405module.exports = destroy
406```
407
408By default, Huncwot will make those actions available under `/widgets` route through HTTP methods and in accordance to **BREAD** principle.
409
410### View
411
412Huncwot uses [Vue.js](https://vuejs.org/) in the view layer to create and mangage components.
413
414### Routes
415
416You can define a route using one of HTTP verbs e.g. `.get()`, `.post()`, `.put()`, `.patch()` or `.delete()` - it takes a string which defines a desired path and a function that defines a action which will be exectued once the route is hit. The action takes the incoming `request` as its parameter and returns a `response` that will be send to the client. The response is represented as a JavaScript object which must have at least `body` and `statusCode` keys. By conventions, a return of string value is considered to be a `200` response of type `plain/text` with `body` set to that string. There is also a `reply` helper function which allows to create responses with `application/json` type out of JavaScript objects.
417
418### Parameters
419
420There are two kinds of parameters possible in a web application: the ones that are sent as part of the URL after `?`, called *query string* parameters; and the ones that are sent as part of the request `body`, referred to as POST data (usually comes from an HTML form or as JSON). Huncwot does not make any distinction between query string parameters and POST parameters, both are available in the request `params` object.
421
422### GraphQL
423
424Huncwot uses [Apollo](http://dev.apollodata.com/) on the client and on the server. Type definitions are stored at the top level in `graphql/`. Resolvers may be grouped in modules underneath `graphql/` directory. GraphQL service endpoint is `/graphql`.
425
426Both schema and resolvers can be auto-generated with placeholder data using `demo` template of `huncwot new`:
427
428```
429huncwot new --template demo
430```
431
432Here's an example of a Vue.js component with a collocated GraphQL query that communicates with the built-in `/graphql` endpoint.
433
434```js
435<template>
436 <div class="content">
437 <h2>Widgets</h2>
438 <ul>
439 <li v-for="widget in widgets">{{ widget.name }}</li>
440 </ul>
441 </div>
442</template>
443
444<script>
445import gql from 'graphql-tag';
446
447const query = gql`
448 query {
449 widgets {
450 name
451 }
452 }
453`
454
455export default {
456 data() {
457 return {
458 widgets: []
459 }
460 },
461
462 apollo: {
463 widgets: {
464 query
465 }
466 }
467}
468</script>
469```
470
471## Modules
472
473Huncwot comes with a set of modules that enable common functionalities
474
475### Auth
476
477```js
478app.use(auth({ users: { 'admin': 'secret' }}))
479```
480
481### Static
482
483```js
484app.use(static('./public'))
485```
486
487## Examples
488
489* [Huncwot Example App](https://github.com/zaiste/huncwot-example-app)
490* [ToDo with Vue.js (Vuex, Vue Router), Node.js and TypeScript](https://github.com/zaiste/huncwot-component-app)
491
492## 3-rd Party Integrations
493
494### [nunjucks](https://mozilla.github.io/nunjucks/) integration
495
496```js
497const Huncwot = require('huncwot');
498const { html } = require('huncwot/response');
499const nunjucks = require('nunjucks');
500
501const app = new Huncwot();
502
503nunjucks.configure('views', { autoescape: true });
504
505app.get('/', request => {
506 return html(nunjucks.render('index.html', { username: 'Zaiste' }));
507})
508
509app.listen(3000);
510```
511
512In your project create `views/` directory with the following `index.html`
513
514```html
515<!DOCTYPE html>
516<html>
517<head>
518 <title>Nunjucks Example</title>
519</head>
520<body>
521 <h1>Hello {{ username }}</h1>
522</body>
523</html>
524```
525
526## Roadmap
527
528Huncwot keeps track of the upcoming fixes and features on GitHub Projects: [Huncwot Roadmap](https://github.com/zaiste/huncwot/projects/1)
529
530## Bug reports
531
532We use *Github Issues* for managing bug reports and feature requests. If you run
533into problems, please search the *issues* or submit a new one here:
534https://github.com/zaiste/huncwot/issues
535
536Detailed bug reports are always great; it's event better if you are able to
537include test cases.
538