Getting started

The only supported way of running Litsor is with Docker. First create an empty directory for your project and create the required directories:

1
2
3
4
mkdir test-project
cd test-project

mkdir endpoints links models scripts

The quickest way to get Litsor running is by a single docker run command.

1
docker run -d --name litsor -p 88:80 -v `pwd`:/app/data -e "litsor_logTo=console" -e "litsor_reload=1" litsor/core:2.0.2

Please check the latest version on Docker hub. This will start a Litsor instance which runs a local SQLite database and reads its config from the current directory. To stop this application, run docker stop litsor followed by docker rm litsor.

Alternatively, you can create a docker-compose.yml file. This file contains a Docker configuration for a single project.. Create this file with the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: '2'

services:
litsor:
image: litsor/core:2.0.2
ports:
- 88:80
volumes:
- ./:/app/data
links:
- mysql
environment:
litsor_reload: '1'
litsor_database: mysql:root:password@mysql/litsor
mysql:
image: mysql:5.7
environment:
MYSQL_DATABASE: litsor
MYSQL_USER: username
MYSQL_PASSWORD: password
MYSQL_ROOT_PASSWORD: password
ports:
- 3306:3306
volumes:
- mysql:/var/lib/mysql

volumes:
mysql:
external:
name: litsor_test_db

This example also includes a MySQL database for persistent storage. A volume is created to store the database. You must first create this database manually and then startup the application:

1
2
docker volume create --name=litsor_test_db
docker-compose up -d

Again, you can access Litsor on port 88. You may also access the database using your favourite application on localhost port 3306, with username “username” and password “password”.

You can stop the application with docker-compose down..

The logs are accessible via docker logs -f litsor (with first option) or docker-compose logs -f litsor (when using Docker compose). Stop watching the logs with ctrl + c. You will find the following lines in the logs:

1
2
A new secret key was generated and stored in data/secret.key
Admin token: 25YvTJb6pEUixsxl4eLoFvEFhVaDMngmkuLnDWL9bnYyOCa0qK9+neYsAkMGjo5c

You can confirm that the secret key is there by running ls. The secret key is used for generating the admin token and encrypting / decrypting data. You only get this output when no secret key was found, so you will not get it again on a restart. Save the admin token in a safe place. You need it for administrative tasks.

Now the application is running, you may try to execute a query. Download a GraphQL client app (for example the GraphiQL electron app app). Open that application, fill in “http://localhost:88/graphql” as the GraphQL endpoint and click th “Edit HTTP headers” button. Add a header with name “Authorization” and value “Bearer TOKEN”. Replace TOKEN by the admin token you got in the logs. Now run the following query:

1
2
3
4
5
{
_modelsConfig {
id
}
}

This query lists your current models. Those are the building blocks of your data structure.

Create your first model

Models define your data structure. Create a directory models and add a file models/post.yml with the following contents:

1
2
3
4
5
6
7
8
9
10
11
12
id: Post
description: Post
storage: Internal
properties:
subject:
type: string
maxLength: 128
body:
type: string
required:
- subject
- body

When you are watching the logs, you will notice a line “Reloading models” on saving this file. This is because the “reload” environment variable was provided. It will also provide helpful errors when the config files are invalid. Remove the first line for example and see what happens.

If no errors appeared, you may try the following query in the GraphQL app:

1
2
3
4
5
6
7
8
9
{
listPost {
count
items {
id
subject
}
}
}

That gives an empty list, since we have not created any items yet. Try the following query to create a post:

1
2
3
4
5
6
7
8
mutation {
createPost(input:{
subject: "Test",
body: "Hello world"
}) {
id
}
}

Running the first query again will now list the new post. You might check out the documentation explorer on the right to view the available fields and parameters.

Create an endpoint

To create a new endpoint, add a file called homepage.yml in the endpoints directory.

1
2
3
4
5
6
7
8
9
id: Homepage
path: /
method: GET
script: PostsPage
params: {}
output:
mime: application/javascript
schema:
type: object

Each callback is handled by a script, PostsPage in this example. We need to create that script as well. Create a file users-page.scr in the scripts directory:

1
2
3
4
5
6
7
8
9
10
11
12
13
# PostsPage

/posts = query {
posts: listPost {
items {
id subject body
}
}
} pick /posts/items

/ = {
body: /posts
}

Wait for the scripts to be reloaded (check the logs) and go to http://localhost:88/
You will now see the lists of posts in JSON format. This is useful for REST API endpoints, but not so for the homepage. Litsor can also serve basic HTML pages. Change the last lines to:

1
2
3
/ = {
body: / handlebars ("{{#each posts}}<div>{{subject}}</div>{{/each}}")
}

Also change the output details in the endpoints/homepage.yml:

1
2
3
4
output:
mime: text/html
schema:
type: string

Refresh the homepage. You will now see a list of the posts in HTML.

Extending the GraphQL API

By default, the GraphQL API comes with the basic CRUD actions and all fields that are available in the model. You can extend the API by adding your own fields. These extensions are caled “links”. They link a field in the GraphQL schema to a script. Add a file links/reading-time.yml:

1
2
3
4
5
6
7
8
9
id: ReadingTime
context: Post
field: readingTime
script: ReadingTime
params: {}
variables: {}
outputSchema:
type: integer
outputMultiple: false

And a script in scripts/reading-time.scr:

1
2
3
4
5
6
7
8
# ReadingTime

/body = query {
Post(id: /parent/id) { body }
} pick /Post/body

/words = length /body split " "
/ = round /words * 0.3

Make a few changes to the PostsPage script. Add the readingTime field and add it to the Handlebars template. The result is:

1
2
3
4
5
6
7
8
9
10
11
12
13
# PostsPage

/posts = query {
posts: listPost {
items {
id subject body readingTime
}
}
} pick /posts/items

/ = {
body: / handlebars ("{{#each posts}}<div>{{subject}} ({{readingTime}} seconds)</div>{{/each}}")
}

You can view the result in the browser. All posts do have an estimated reading time added.