Building a web application can be especially challenging when the developer has to set up and configure multiple tools, which might include a database, server, building tools, and storage servers. Setting up all of these can take several hours of development time, dragging out the time it takes for the product to go live.
The Jamstack was created to relieve developers of this burden, allowing them to focus on their code or business logic while developing flexible, scalable, high-performance, and maintainable web applications. In this tutorial, we'll learn how to build a Jamstack app with Tigris.
To follow along with this article, you'll need Docker and Node.js *≥*v14 installed on your machine. Let's get started!
- What is Tigris?
- Why use Tigris?
- Getting started with Tigris
- Starting Tigris with Docker
- Create a RESTful web app
- Test the application
What is Tigris
Tigris is an open source, modern, scalable backend for developing real-time websites and apps. With its zero-ops approach, Tigris frees developers to concentrate on their applications. Tigris is a data platform that allows developers to build real-time applications without having to deal with all of the tedious infrastructure management, allowing for more code-focused development and less data infrastructure.
It's important to keep in mind that at the time of writing, Tigris is in beta.
Why use Tigris?
Let's consider some of the reasons you might choose Tigris for your web applications. For one, it handles all deployment, configuration, security, monitoring, and maintenance tasks, as well as infrastructure components. It is an alternative to traditional data handling methods like query tuning, index management, and maintenance.
Every Tigris application includes a database by default, so there is no need to learn a new database language. Tigris maintains data control while avoiding vendor lock-in, and it eliminates data silos and sprawling data infrastructure.
Getting started with Tigris
With the above requirements met, let's get started by scaffolding a Tigris application with the command below:
mkdir tigris-app && cd tigris-app
npx create-tigris-app
The command above will create a tigris-app
folder and scaffold a new Tigris application with the folder structure below:
tigris-app
┣ src
┃ ┣ lib
┃ ┃ ┗ config.ts
┃ ┣ models
┃ ┃ ┗ user.ts
┃ ┣ app.ts
┃ ┗ index.ts
┣ package-lock.json
┣ package.json
┗ tsconfig.json
-
index.ts
: The entry-point for the generated application -
app.ts
: The application class that initializes the Tigris client -
models/user.ts
: Contains the data container and schema for the user collection. Here, all the schemas in the Tigris application are defined
You can modify the project structure however you prefer.
Starting Tigris with Docker
To run the Tigris server, run the command below to run the Docker image for Tigris:
docker run -d -p 8081:8081 tigrisdata/tigris-local
The command above will run Tigris on localhost:8081
. Now, run the application with the command below:
npm run start
The command above will run the default operations configured in the project, but we'll change these later.
Create a RESTful web app
With our application created and running, let's modify the project to create a RESTful web application for a blog website. To get started, install Express with the command below:
npm install express @types/express
Then, create a controllers
folder in the src
directory.
Create the model
Tigris allows you to declare data models as part of the application and then convert them to appropriate objects, like collections. We'll do that in the model's folder. Rename the models/user.ts
file to blog.ts
and replace the existing code with the code snippets below:
import {
TigrisCollectionType,
TigrisDataTypes,
TigrisSchema
} from "@tigrisdata/core/dist/types";
export interface Blog extends TigrisCollectionType {
id?: number;
title: string;
content: string;
description: string
}
export const blogSchema: TigrisSchema<Blog> = {
id: {
type: TigrisDataTypes.INT32,
primary_key: {
order: 1,
autoGenerate: true,
},
},
title: {
type: TigrisDataTypes.STRING,
},
content: {
type: TigrisDataTypes.STRING,
},
description: {
type: TigrisDataTypes.STRING,
},
};
In the code snippet above, we imported TigrisCollectionType
to define the interface type for the blogs we are creating, TigrisDataTypes
to define the data types of the fields in our blog
schema, and TigrisSchema
to define the schema type.
Next, we created the blog
schema and defined the fields. We have an id
that is an auto-generated primary key, as well as a title
, content
, and a description
for each blog.
Create the controllers
With the schema created, let's create the controllers of our application. To get started, create blog.ts
and controller.ts
files in the controllers
directory. In the code snippet below, we'll define an interface to set up the routes for this application in the controller.ts
file:
import express from "express";
export interface Controller {
setupRoutes(app: express.Application);
}
Then, in the controller/blog.ts
file, we'll create the required imports, create a BlogController
class, and define the required variables with the code below:
import express, { Request, Response, Router } from "express";
import { Collection, DB } from "@tigrisdata/core";
import { Blog } from "../models/blog";
import { Controller } from "./controller";
export class BlogController implements Controller {
private readonly db: DB;
private readonly blogs: Collection<Blog>;
private readonly router: Router;
private readonly path: string;
constructor(db: DB, app: express.Application) {
this.blogs = db.getCollection<Blog>("blogs");
this.path = "/blogs";
this.router = Router();
this.db = db;
}
}
In the code snippet above, we imported Collection
and DB
from Tigris, the Blog
schema, and the Controller
interface. Then, we defined the global variables and assigned the appropriate values and method to them in our constructor method.
Now, let's add CRUD methods to the class with the code snippets below:
//...
public createBlog = async (req: Request, res: Response) => {
try {
const newBlog = await this.blogs.insert(req.body);
res.status(200).json({
data: newBlog,
message: "Blog Created!",
});
} catch (e) {
res.status(500).json({
message: "An error occured:" + e,
});
}
}
public getBlogs = async (req: Request, res: Response) => {
try {
const blogs = await this.blogs.findMany(req.body);
res.status(200).json({
data: blogs
});
} catch (e) {
res.status(500).json({
message: "An error occured:" + e,
});
}
}
public getBlog = async (req: Request, res: Response) => {
try {
const blog = await this.blogs.findOne({
id: Number.parseInt(req.params.id),
});
if (!blog) {
return res.status(404).json({
data: blog,
message: "Blog not found!",
});
}
return res.status(200).json({
data: blog
});
} catch (e) {
res.status(500).json({
message: "An error occured:" + e,
});
}
};
public updateBlog = async (req: Request,
res: Response,) => {
try {
await this.blogs.update({ id: parseInt(req.params.id) }, req.body);
res.status(200).json({
message: "Blog Updated"
});
} catch (e) {
res.status(500).json({
message: "An error occured:" + e,
});
}
}
public deleteBlog = async (
req: Request,
res: Response,
) => {
try {
await this.blogs.delete({
id: Number.parseInt(req.params.id),
});
res.status(200).json({
message: "Blog deleted",
});
} catch (e) {
res.status(500).json({
message: "An error occured:" + e,
});
}
};
//...
In the code snippet above, we used the blog's instance, which is the collection for our Blog
schema we accessed through the db.getCollection
function. This function provides us with the method we need to perform CRUD operations in our database.
Then, set up the routes for this method by adding the method below:
//...
public setupRoutes(app: express.Application) {
this.router.post(`${this.path}/`, this.createBlog);
this.router.get(`${this.path}/`, this.getBlogs);
this.router.get(`${this.path}/:id`, this.getBlog);
this.router.put(`${this.path}/:id`, this.updateBlog);
this.router.delete(`${this.path}/:id`, this.deleteBlog);
app.use("/", this.router);
}
//...
In the code snippet above, we used the Express router, which is accessible through the router
instance, to define the route's endpoints for each method in our BlogController
. Lastly, call the setupRoutes
method in the constructor method:
constructor(db: DB, app: express.Application){
// ...
this.setupRoutes(app);
//...
}
Configure the application
With the controllers and routes set up, let's configure the application, initialize Tigris, create a database for the application, and create a blog collection. To get started, delete the code in the app.ts
file and replace it with the code snippets below:
import { DB, Tigris } from "@tigrisdata/core";
import { Blog, blogSchema } from "./models/blog";
import express from "express";
import { BlogController } from "./controllers/blog";
export class Application {
private readonly tigris: Tigris;
private db: DB
private readonly app: express.Application;
private readonly PORT: string | number;
private readonly dbName: string;
constructor(tigris: Tigris) {
this.tigris = tigris
this.app = express()
this.PORT = 3000;
this.dbName = 'tigris_blog'
this.setup();
}
public async setup() {
this.app.use(express.json());
await this.initTigris();
}
public async initTigris() {
//create a database
this.db = await this.tigris.createDatabaseIfNotExists(this.dbName);
console.log('database created successfully')
//register collections
await this.db.createOrUpdateCollection<Blog>('blogs', blogSchema);
//setup controllers
new BlogController(this.db, this.app);
}
public start() {
this.app.listen(this.PORT, () => {
console.log(`Server is running at ${this.PORT}`)
})
}
}
In the code above, we imported and defined the modules and global variables we need in this class. We create a setup method where we create the middleware of the application and initialize Tigris by calling the initTigris
method, which will create a database and our blog collection. We also create a start method to run the application on port 3000
, as defined by the PORT
variable.
Now, let's modify the index.ts
file to call the start
method in our Application
class, which will start running the server:
//.. app.start();
Lastly, ensure that your Tigris client serverUrl
, which was initialized in the lib/config
file, is listening to localhost:8081
, as shown in the code snippet below:
import {Tigris} from "@tigrisdata/core";
export class Config {
public initializeTigrisClient(): Tigris {
return new Tigris({
serverUrl: "localhost:8081",
insecureChannel: true,
});
}
}
Test the application
Now, let's test the application using cURL. Run the command below to create a new blog:
curl http://localhost:4000/blogs \
-X POST \
-H 'Content-Type: application/json' \
-d '{
"title": "Build a web app with Node.js",
"content": "Content goes here",
"description":"Description goes here"
}'
You should get a response as shown in the screenshot below:
Conclusion
In this tutorial, we've learned how to build a Jamstack application with Tigris. We started by introducing Tigris and discussing why you should consider using it. Then, as a demonstration, we built a RESTful web application to manage a blog.
Tigris is an exciting tool, and I recommend checking out the Tigris documentation to learn more. I hope you enjoyed this article, and happy coding!
Author: Ekekenta Odionyenfe