Frontless aims to provide classic web development experience with modern approach.
Classic MVVM approach significanly complicates work with data. In fact, on practice, a frontend developer would end up writing the code that would be better performed by server rather than a client. I believe that the server has to be responsible for things like routing, data requests, user state, and and some cases component's view-model. Theese are routines that the server does better than browser.
- It is just an ExpressJS application.
- It uses FeathersJS on client and server.
- It is built with ❤️ RiotJS.
- It provides natural routing
page.riot -> GET /page
- It allows to update components' state directly from server response
- Clone this repo or use NPX
npx create-frontless <app-name>
- Setup a MongoDB Server (optional). Frontless reads
MONGODB_URI
environment variable.
# config.env
MONGODB_URI=mongodb://localhost:27017/frontless
- Install dependencies and start dev. server
npm run install
npm start
Оpen http://localhost:6767 in your browser. Navigate to the playground for examples
Essential understanding of following technologies is recommended.
Server | Client |
---|---|
Routing (Express.JS ) | Navigation (Turbolinks) |
View Model (FeathersJS) | Data Representation (RiotJS) |
Layout Rendering (RiotJS SSR) | User input (RiotJS) |
Session / User State (Express.js) | JWT, Cookies |
Realtime (Feathers, SocketIO) | FeathersJS Client |
DB Interface (FeathersJS Client) | Rest/IO (FeathersJS Client) |
All files ending with *.riot
placed in the pages
become site pages, much like php scripts or html pages.
[index.riot -> GET /
, page.riot -> GET /page
]
Passing positional argument to the page is possible trough @
modifier. A semicolon-separated string after @
will be parsed as positional arguments.
For example consider following request:
GET /page@some_id;data?q=1
This request will fetch page.riot
and pass positional arguments into 'request.params.args':
export default {
async fetch(props){
const {req} = props;
const [user_id, data] = req.params.args;
}
}
All RiotJS components included in pages will render after all data is fetched.
Use method fetch(props)
in your components to make db queries and setting components' state on the server.
Unlike similar method in next.js
, in Frontless you can fetch data in any children component
and your page will be rendered after all fetch operations are complete.
export default {
async fetch(props) {
const {params, session} = props.req
const userProfile = await db.users.get(session.user.id)
this.update({
username: session.username,
userProfile
})
}
}
Some API requests can return a ready view-model for a specific component. After it happens the target component will update its state from received response. This is convenient whenever you want to update the view after a request is done. Given that, the server should return a ready view-model which eliminates extra steps you would do to handle response.
Normally, you should follow 3 steps to make it work:
- Give your component an unique id
export default {
id: 'uniq-id', // target id
state: {
message:''
}
...
}
- Use method app.setState() to compose a response
app.use('myservice',{
async create(data){
return app.setState('uniq-id', {
message: 'Hello!'
})
}
})
- On the client make a call to service method which suppose to return new state
client.service('myservice').create({})
Notice that you don't need to handle API call as the server supposed to return ready view-model for your component. The UI will update automatically. However, you still nedd to handle loading states and errors.
Access to pages can be controlled trough options set in the access
property:
export default ()=> ({
access: {
loggedIn: true,
},
state:{},
...
})
By default two options are awailable: loggedIn
and group
.
Authentication is impemented with @feathersjs/authentication-local module. In order to customize user model you need to modify verifier class and the plugins
- Under no circumstances, It is NOT recommended to turn off the CORS middlewares.
- When working with Riot Components, it is NOT recommended to use sensitive variables or use any sentive data as open text
- When working with Riot Components, is is HIGHLY recommended to use functional approach. Every component should be returned from a function like
export default ()=> ({...component})
. This is needed to avoid module caching
- Anton Nesterov - @nesterow
- Gianluca Guarini - @GianlucaGuarini - riot/hydrate, Riot.js
This project is licensed under the MIT License - see the LICENSE.md file for details
- Async SSR
- Natural Routing
- Database Interface
- Users and Sessions
- Server Sent State (w/feathers.js)
- Socket IO (w/feathers.js)
- Plugin support
- Configuration
- Deployment scripts
- PWA Bootstrap [80%]
- Documentation [15%]