Tealight is a web and REST framework for Pharo
It is a project defining a few extensions to the Teapot web framework to make the (tea) time you spend with the Pharo Teapot system even easier.
You can install Tealight by executing the following load scripts:
Metacello new
repository: 'github://astares/Tealight:main/src';
baseline: 'Tealight';
load
Alternatively you can install Tealight also from the Pharo catalog using the Catalog Browser right from within your Pharo image.
After you have the framework installed you can easily start a Tealight web server by selecting
"Tealight" -> "WebServer" -> "Start webserver"
from the Pharo world menu. Internally this starts a Teapot server with some defaults.
You can also easily stop the server from the Tealight web server menu by using "Stop webserver" or open a webbrowser on the server by using "Browse".
After you started the server you can easily access the running Teapot instance from your code or playground
TLWebserver teapot
You can easily experiment with Teapot routes, for instance using
TLWebserver teapot
GET: '/hi' -> 'HelloWorld'
If you point your browser to http://localhost:8080/hi you will receive your first "HelloWorld" greeting.
If you open an inspector on the Teapot instance
TLWebserver teapot inspect
you will see that a dynamic route was added:
So you can dynamically add new routes for GET, POST, DELETE or other HTTP methods interactively.
We recommend to read the Teapot chapter of the Pharo Enterprise Book to get a better understanding of the possibilities of the underlying Teapot framework.
Lets add another dynamic route on our default Teapot, this time a dynamic route with a block as an action. The block itself receives the request as an argument:
TLWebserver teapot
GET: '/hello/<user>' -> [:req | 'Hello ', (req at: #user)]
You can now use your web browser again to call the newly defined URL. This time you need to give a parameter when you point the webbrowser to the new URL.
http://localhost:8080/hello/Pharo returns Hello Pharo while
http://localhost:8080/hello/Smalltalk returns Hello Smalltalk
There is a new tool called Teaspoon that was added as a custom inspector tool on dynamic routes by Attila Magyar. Attila is the author of Teapot. Let's see how we can access this nice tool.
First inspect our Teapot instance again
TLWebserver teapot inspect
but this time click on the dynamic route that we added lately in the displayed list of dynamic routes. The inspector will navigate into another inspector displaying the dynamic route instance. There is a second custom tab extension in this inspector called "Teaspoon":
In this inspector tool you see the HTTP method (here GET) and the full URL of your dynamic route. You can change the URL by replacing with your name and click on "Execute Request".
This will perform the request and display the result in the inspector again. So you can interactively test your routes right from within your Pharo image without switching to external tools.
The Teaspoon tool is especially useful when you experiment with route patterns or if you want to define, implement and test a REST based API with Pharo.
If you (by accident) messed up with a single route defintion during your experiments the context menu in the inspector comes to the rescue:
to delete a single route or all routes. You can even clean up via script:
TLWebserver teapot
removeAllDynamicRoutes
For the next part of this tutorial lets remove all defined routes.
While it is nice to experiment with dynamic routes by adding them one by one to the Teapot instance it would be even more convinient
- if we could define the REST API using regular Smalltalk methods
- if we could map each URL easily
To support that Tealight adds a special utility class (called TLRESTAPIBuilder) to help you easily build an API. Lets see how we can use it.
First of all we need to create a simple class in the system either from the browser or with an expression:
Object subclass: #ExampleRESTAPI
instanceVariableNames: ''
classVariableNames: ''
package: 'MyApp-REST-API'
Now we can define a class side method:
greeting: aRequest
<REST_API: 'GET' pattern: 'hello'>
^'HelloWorld from Pharo'
As you see we use a pragma in this class marking the class side method as REST API method and defining the kind of HTTP method we support as well as the function path for our little REST service.
Now we can use the utility class to generate the dynamic routes for us sending a message to our class ending up in our method:
TLRESTAPIBuilder buildAPI
This creates the dynamic routes for us:
Also that by default there is an "api" prefix generated into the URL for all REST based methods so you need to point your browser to:
http://localhost:8080/api/hello
If you dislike the default API prefix you can customize this for your own needs:
TLRESTApiURLPathBuilder withoutPrefix "have no API prefix in the URL"
which would result in http://localhost:8080/hello or
TLRESTApiURLPathBuilder useAPIPrefix: 'mykillerapp/myapi' "have a custom API prefixin the URL"
to use a custom prefix for the API location http://localhost:8080/mykillerapp/myapi/hello
There are different architectural styles to define a web based API. As with any regular API you might want to version your API and use a specific part of the URL to depict the version that is used.
If you want to do this you can use another pragma to
callMe: aRequest
<REST_API: 'GET' versions: #('v1') pattern: 'hello'>
self halt
Now rebuild the whole API quickly by cleaning up and regenerating the dynamic routes again (using the default prefix):
TLRESTApiURLPathBuilder useStandardPrefix.
TLWebserver teapot removeAllDynamicRoutes.
TLRESTAPIBuilder buildAPI
This will now generate a http://localhost:8080/api/v1/hello route - again with a call that ends up in our own method. The v1 part in the URL shows the user of the API that this is for version 1 of the interface.
As you may have guessed already you can give more than one version in the pragma. A function that is supported in two version can be annontated like this:
callAnother: aRequest
<REST_API: 'GET' versions: #('v1' 'v2') pattern: 'hello'>
^'A method that is supported in both versions'
will create two dynamic routes:
Over time the API that you provide in your webapp might change from one version of the web interface to another.
For instance a web method might need to be renamed to fix a typo. This can easily be done now:
callRenamed: aRequest
<REST_API: 'GET' versions: #('v1') pattern: 'oldName'>
<REST_API: 'GET' versions: #('v2') pattern: 'newName'>
^'A method that was renamed between two API versions'
will create two dynamic routes:
So in version v1 you still support the old name but the users of your API are encouraged to use the new name in version v2.
It might also be necessary to move the location of a method in a higher version of your interface - but you still might want to stay compatible to the old version:
callMoved: aRequest
<REST_API: 'GET' versions: #('v1') pattern: 'firstAppearance'>
<REST_API: 'GET' versions: #('v2') pattern: 'moved/newAppearance'>
^'A method that was moved between two API versions'
will create two dynamic routes:
So the old route could be used as well as the new one - but still a single Pharo method is called.
Tealight makes it easy to experiment with Teapot framework and allows you to easily generate a web based API. While we demonstrated most code here with a HTTP GET all the examples could be done with PUT, DELETE, POST, ... as well.