Skip to content
Ralf Vogler edited this page Jun 25, 2015 · 14 revisions

A tour of Opa

Opa is a full-stack open source web development framework for JavaScript that lets you write secure and scalable web applications.

Opa generates standard Node.js/MongoDB applications, natively supports HTML5 and CSS and automates many aspects of modern web application programming: Ajax/Comet client-server communication, event-driven and non-blocking programming models.

Opa enables to write full-stack applications:

  • server (backend) programming (running on Node.js),
  • client (frontend) programming (compiled to JavaScript) and
  • database programming (using MongoDB).

Throughout the pages of this manual, we will introduce you to the many features of Opa. It will help if you have some knowledge of programming and web technology (HTML and CSS in particular).

Full-stack

Opa handles all aspects of web programming: frontend (client code), backend (server code) and database queries are all written in one consistent language and compiled to standards of the web: JavaScript frontend, Node.js backend and MongoDB for the database. Other targets are planned, making Opa a gateway to web programming.

You can write a complete Opa program without thinking about the client-server distinction and the Opa compiler will distribute the code as needed for you and take care of all the communication. Should you need to tweak the choices made by the compiler (for instance to improve the application performance) it's very easy with simple keywords like client, server and more for fine-tuning.

// Opa decides
function client_or_server(x, y) { ... }
// Client-side
client function client_function(x, y) { ... }
// Server-side
server function server_function(x, y) { ... }

The database code can also be written directly in Opa. Opa supports the major NoSQL databases: CouchDB and MongoDB. The latter requires no configuration at all and is recommended for beginners, while the former offers state-of-the-art reliability and performance.

Easy workflow

To write an application, first type the code in your favorite editor. The simplest "Hello, world" application in Opa is written in just a few lines:

Server.start(
   Server.http,
   { title: "Hello, world"
   , page: function() { <h1>Hello, world</h1> }
   }
)

The program can be compiled and run with the following single command line: opa hello.opa --

The resulting application can be opened in your favorite browser at http://localhost:8080

Familiar syntax

Opa syntax is inspired by popular programming languages, most notably, JavaScript. Below is an extract of a real Opa program:

function createUser(username, password) {
  match (findUser(username)) {
  case {none}:
    user =
      { username: username
      , fullname: ""
      , password: Crypto.Hash.sha2(password)
      };
    saveUser(user);
  default:
    displayMessage("This username exists");
  };
  Client.goto("/login");
}

Opa however extends the classical syntax with advanced features specific to the web. HTML fragments can be inserted directly without quotes: line = <div id="foo">bar</div>;

CSS selectors readily available: selector = #foo;

And a pointer-like syntax allows to apply a given content to a selector: *selector = line;

Opa provides event-driven programming. For instance, running a function when an event is triggered is accomplished in the following way:

function action(event) {
  #foo = <div id="bar" />;
}
...
<div onclick={action} />

The best place to start looking at the features of Opa and their syntax is the reference card.

Static typing

One of the most important features of Opa is its typing system. Although Opa may look like and has many advantages of dynamic programming languages, it is a compiled language which relies on a state-of-the-art type system.

Opa checks the types at compile time, which means that no type error can happen at runtime. For instance, the following code foo = 1 + "bar"; raises the following error at compile time:

Type Conflict
  (1:7-1:7)           int
  (1:11-1:15)         string

  The types of the first argument and the second argument
    of function + of stdlib.core should be the same

Unlike C or Java, you don't have to annotate types yourself as Opa features almost complete type inference. For instance, you can just write:

function foo(s) {
   String.length(s);
}
function bar(x, y) {
   foo(x) + y;
}

and the Opa compilers automatically infers the types, as if you've written:

int function foo(string s) {
   String.length(s);
}
int function bar(string x, int y) {
   foo(x) + y;
}

This system will become your wingman while you code. For instance, we will present four types of errors that are caught at compile time. The examples are taken from a real work on Opa program named webshell, available at http://github.com/hbbio/webshell.

If you write:

element =
  <div>
    <span>{prompt({none})}</span>
    <span>{expr}
  </div>
  <div>{Calc.compute(expr)}</div>;

The compiler will tell you that there is an "Open and close tag mismatch ,found at (48:8-48:11), vs .".

If you write:

case {some: 13}: #status = "Enter"; callback(get());
case {some: 37}: #status = "Left"; move({lef});
case {some: 38}: #status = "Up"; move({up});
case {some: 39}: #status = "Right"; move({right});

The compiler will tell you that the type of this function is not right. You are using a type { lef } when a type { left } or { right } or { rightmost } or { up } or { down } is expected. The latter type is not declared anywhere in the code, but rather was inferred by the Opa compiler from the rest of the code.

If you write:

previous = Dom.get_content(#precaret);
#precaret = String.sub(0, String.lenght(previous) - 1, previous);
#postcaret += String.get(String.length(previous) - 1, previous);

the compiler will tell you that String module has no lenght function and will suggest that maybe you meant length or init instead?

If you write:

previous = Dom.get_content(#postcaret);
#postcaret = String.sub(1, String.length(previous) - 1, previous);
#precaret =+ String.get(previous);

the compiler will tell you that String.get takes 2 arguments, but only 1 is given. And will suggest that the 1st-argument is of type int.

Opa type system not only manages basic types but complex data-structures, functions and even modules! Check the following chapter for a full presentation.

Database

Opa has extensive support for MongoDB (and, to lesser extent, CouchDB) offering a state-of-the-art solution for data storage and retrieval.

Database values are declared by stating their type: database type /path; for instance database int /counter;

In the line above, /counter is called a path, as accessing stored values bears similarities to browsing a filesystem. Getting a value from the database is simply accomplished with: /counter while storing (or replacing) a value with: /path <- value

You can store complex datastructures in the database, like maps. A map is a datastructure that associates a value to each key. The path system recognize such datastructures and allows to specify a key directly in the path. For instance, you can write:

database stringmap(string) /dictionary;
...
/dictionary[key];
...
/dictionary[key] <- value;
...

Getting started in seconds

Opa offers scaffolding for easy project creation. Just write opa create myapp

This will create a skeleton of a new app named myapp based on the MVC architecture. Run it with:

cd myapp
make run

Edit the sources in the src and resources in the resources directory. You can also try opa create --help for more options.

Going further

In the following chapters, we will introduce you to various features and use-cases of Opa. Each chapter concentrates on writing one specific application, and on how best to achieve this using combination of skills developed in previous and current chapter. At the end of the manual, additional reference chapters introduce all the concepts of the language and the platform in more detail.

If you have any question or feedback, do not hesitate to contact us. A few ways to get in touch:

We will be there. Now it's up to you to write great apps!