Customize the client
Now that we have both a client and a backend, we can start to build a project with Squid! Developing with Squid allows us to easily use a database to insert, read, update, and delete data from the client-side without worrying about setting up an API.
At this point, we can begin to perform CRUD operations in our database straight from our client side code.
- React
- Typescript
- Angular
The Project
This guide explains how to create a simple React application that utilizes the Squid React SDK with a Squid backend. The application inserts random users into the database and displays their information on a React website.
Insert
Squid organizes data into collections and documents. Documents are individual records in the database (such as rows in a table), while collections are groups of documents (similar to unstructured tables). To get a reference to a collection in React, use the useCollection
hook. Squid's built-in database will automatically create a new collection for this collection reference:
const userCollection = useCollection<User>('users');
To insert into the new collection, create a new document and insert data into the new document:
userCollection.doc(userId).insert({
id: userId,
email: email,
age: Math.ceil(Math.random() * 100),
});
A full insert example is below. For now, we will set the email attribute to be the user’s id@email.com
. Create a new components directory with a createUser.tsx
file. In src/components/createUser.tsx
:
import { useCollection } from '@squidcloud/react';
// Define your type
type User = { id: string; email: string; age: number };
export default function CreateUser() {
const userCollection = useCollection<User>('users');
const insert = () => {
const userId = crypto.randomUUID();
const email = `${userId}@email.com`;
userCollection.doc(userId).insert({
id: userId,
email,
age: Math.ceil(Math.random() * 100),
});
};
return (
<>
<button onClick={insert}>Insert new user</button>
</>
);
}
Read
To get the users
collection reference from Squid, you first bring in the collection using the useCollection
hook:
const collection = useCollection<User>('users');
You can now get the list of users, which will be streamed to the client and kept up-to-date in real-time:
const users = useQuery(collection.query());
Each user in the users array has a data object. For more querying options see the documentation. Below is a full read example in src/components/readUsers.tsx
:
import { useCollection, useQuery } from '@squidcloud/react';
type User = { id: string; email: string; age: number };
export default function ReadUsers() {
const collection = useCollection<User>('users');
/** The list of users will be streamed to the client and kept up-to-date */
const users = useQuery(collection.query());
return (
<ul style={{ listStyle: 'none', paddingLeft: '0px' }}>
{users.data.map((user) => (
<li key={user.data.id}>
{user.data.email} - {user.data.age}
</li>
))}
</ul>
);
}
At this point, we can update our src/App.tsx
and test the app:
import './App.css';
import CreateUser from './components/createUser';
import ReadUsers from './components/readUsers';
function App() {
return (
<>
<CreateUser />
<ReadUsers />
</>
);
}
export default App;
With Squid's real-time functionality, you can insert data in one tab and instantly see the updates in other tabs without requiring any additional code!
Update
To update a document in the collection, call the update()
method on a DocumentReference
and pass in an object that contains the partial update data as an argument. To do so, we will create a simple HTML form which can update a user’s age:
<form onSubmit={onSubmit}>
<label htmlFor="email">Email: </label>
<input id="email" />
<label>Age: </label>
<input id="age" type="number" />
<input type="submit" value="Update age" />
</form>
We will update the first user with the matching email field specified in the form. In src/components/updateUser.tsx
:
import React from 'react';
import { useCollection, useQuery } from '@squidcloud/react';
type User = { id: string; email: string; age: number };
type EventObj = { value: string }; // Used for form event data access
export default function UpdateUser() {
const collection = useCollection<User>('users');
const users = useQuery(collection.query());
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
// Find the first user with the matching email
const user = users.data.find(
(user) =>
user.data.email == (e.target as unknown as Array<EventObj>)[0].value
);
if (user) {
user
.update({
age: Number((e.target as unknown as Array<EventObj>)[1].value),
})
.then(() => console.log('User updated successfully'))
.catch((error) => console.error('Failed to update user', error));
}
};
return (
<form onSubmit={onSubmit} style={{ padding: '20px' }}>
<label htmlFor="email">Email: </label>
<input id="email" />
<label style={{ marginLeft: '10px' }}>Age: </label>
<input id="age" type="number" />
<input type="submit" value="Update age" />
</form>
);
}
Next we can import our new update component into src/App.tsx
and test our new ability to update a user’s age.
Delete
To delete a document, you can call the delete()
method on a DocumentReference
. Create a new deleteUser.tsx
file where we will create the new component. The component will delete the first user that matches with the specified email field. In src/components/deleteUser.tsx
:
import React from 'react';
import { useCollection, useQuery } from '@squidcloud/react';
type User = { id: string; email: string; age: number };
type EventObj = { value: string }; // Used for form event data access
export default function DeleteUser() {
const collection = useCollection<User>('users');
const users = useQuery(collection.query());
const onSubmit = (e: React.FormEvent) => {
e.preventDefault();
const user = users.data.find(
(user) =>
user.data.email == (e.target as unknown as Array<EventObj>)[0].value
);
if (user) {
const id = user.data.id;
user
.delete()
.then(() => console.log(`User ${id} deleted`))
.catch((error) => console.error('Failed to delete user', error));
}
};
return (
<form onSubmit={onSubmit}>
<label htmlFor="email">Email: </label>
<input id="email" />
<input type="submit" value="Delete user" />
</form>
);
}
After completing the delete component, import it into src/App.tsx
. We are now able to use Squid’s built in database service to accomplish all the major functionality that is required in a database.
Next steps
Congratulations on completing the tutorial! To learn about key features of the Squid backend including security functionality, view the Backend SDK documentation. To learn about the functionality of the Squid Client SDK including more complex data queries, managing secrets, and interacting with your storage buckets, view the Client SDK documentation.
The Project
This guide explains how to create a simple command line application that utilizes the Squid Client SDK with a Squid backend. The application will read input from the command line and perform basic database operations based on user input.
Reading command line input
To begin customizing the client, we first need to read input from the command line. Create a readInput()
function, which will have the squid
object we initialized in the previous step as a parameter. We will use the readline module in order to read user input. The readInput()
function will recursively call itself and read input until the user chooses to exit
the program. We will execute the main()
function at the bottom of the file:
import { Squid } from '@squidcloud/client';
import * as readline from 'readline';
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
function main() {
const squid = new Squid({
appId: 'YOUR_APP_ID',
region: 'YOUR_REGION', // example: 'us-east-1.aws'
environmentId: 'dev | prod', // choose one of 'dev' or 'prod'
squidDeveloperId: 'YOUR_SQUID_DEVELOPER_ID',
});
readInput(squid);
}
function readInput(squid: Squid) {
rl.question('Usage [exit / insert / read / update / delete]: ', (input) => {
if (input.toLowerCase() === 'exit') {
rl.close();
process.exit(0);
} else if (input.toLowerCase() === 'insert') {
console.log('Insert placeholder');
readInput(squid);
} else {
console.log('Please select a recognized command');
readInput(squid);
}
});
}
main();
Insert
Squid organizes data into collections and documents. Documents are individual records in the database (such as rows in a table), while collections are groups of documents (like tables themselves, but unstructured). To access a Squid collection, use the collection()
method with the collection name as the first parameter.
To create a new document in Typescript, use the insert()
method on a document reference. Document references can be initialized with a document ID, which you can learn more about in our documentation. Create a new insertUser()
function:
import * as crypto from 'crypto';
...
function insertUser(name: string, squid: Squid) {
type User = { id: string; name: string; age: number };
const userId = crypto.randomUUID();
squid
.collection<User>('users') // users collection
.doc(userId) // docId = userId
.insert({
id: userId,
name,
age: Math.ceil(Math.random() * 100),
})
.then(() => console.log("New user created"))
.catch((err) => console.error("Error creating user: ", err));
}
Read
Squid provides a robust and powerful query system. To simply get a reference to the collection of users from Squid, there are multiple different options which suit different use cases, such as subscribing to changes in the collection or accessing it a single time. In our case, we will use the dereference()
method to receive the document data directly. Create a readUsers()
function:
function readUsers(squid: Squid) {
type User = { id: string; name: string; age: number };
squid
.collection<User>('users')
.query()
.dereference()
.snapshot()
.then((user) => {
console.log(user);
});
}
Now that we have the ability to insert into and read the collection, we can begin to test our application. Here is the code example in index.ts
up to this point. Note that we will wrap the body of the readInput()
function in a timeout so that output is printed in the correct order:
import { Squid } from '@squidcloud/client';
import * as readline from 'readline';
import * as crypto from 'crypto';
const rl = ...
function main() {
const squid = ...
readInput(squid);
}
function readInput(squid: Squid) {
setTimeout(() => {
rl.question('Usage [exit / insert / read / update / delete]: ', (input) => {
if (input.toLowerCase() === 'exit') {
rl.close();
process.exit(0);
} else if (input.toLowerCase() === 'insert') {
rl.question('Name: ', (name) => {
insertUser(name, squid);
readInput(squid);
})
} else if (input.toLowerCase() === 'read') {
readUsers(squid);
readInput(squid);
} else {
console.log("Please select a recognized command");
readInput(squid);
}
})
}, 1500)
}
function insertUser(name: string, squid: Squid) {
...
}
function readUsers(squid: Squid) {
...
}
And here is example usage of the app:
Update
To update a document in the collection, we call the update()
method on a DocumentReference
and pass in an object that contains the partial update data as an argument. To do so, we will create an updateUser()
function which will update a user's name and/or age:
function updateUser(squid: Squid, id: string, name?: string, age?: number) {
type User = { id: string; name: string; age: number };
let updateObj;
// updateObj should be partial or complete
if (name && age) {
updateObj = { name, age };
} else if (name) {
updateObj = { name };
} else if (age) {
updateObj = { age };
} else {
console.log('Please update either a name or age');
return;
}
squid
.collection<User>('users')
.doc(id)
.update(updateObj)
.then(() => console.log(`User ${id} updated`))
.catch((err) => console.error(`Error updating user ${id}: `, err));
}
Next, we can update readInput()
to execute the update function. To use the update function, you can copy a user's id after executing the read
command:
function readInput(squid: Squid) {
setTimeout(() => {
rl.question('Usage [exit / insert / read / update / delete]: ', (input) => {
if (input.toLowerCase() === 'exit') {
...
} else if (input.toLowerCase() === 'insert') {
...
} else if (input.toLowerCase() === 'read') {
...
} else if (input.toLowerCase() === 'update') {
rl.question('Id: ', (id) => {
rl.question('Name: ', (name) => {
rl.question('Age: ', (age) => {
updateUser(squid, id, name, parseInt(age));
readInput(squid);
})
})
})
} else {
...
}
})
}, 1500)
}
Delete
To delete a document, you can call the delete()
method on a DocumentReference
. Create a deleteUser()
function, which takes in an id as the second parameter:
function deleteUser(squid: Squid, id: string) {
type User = { id: string; name: string; age: number };
squid
.collection<User>('users')
.doc(id)
.delete()
.then(() => console.log(`User ${id} deleted`))
.catch((err) => console.error(`Error deleting user ${id}: `, err));
}
Next, update readInput()
to execute the delete function:
function readInput(squid: Squid) {
setTimeout(() => {
rl.question('Usage [exit / insert / read / update / delete]: ', (input) => {
if (input.toLowerCase() === 'exit') {
...
} else if (input.toLowerCase() === 'insert') {
...
} else if (input.toLowerCase() === 'read') {
...
} else if (input.toLowerCase() === 'update') {
...
} else if (input.toLowerCase() === 'delete') {
rl.question('Id: ', (id) => {
deleteUser(squid, id);
readInput(squid);
})
} else {
...
}
})
}, 1500)
}
After completing delete, we are now able to use Squid’s built in database service to accomplish all the major functionality that is required in a database. Here is the final index.ts
file:
import { Squid } from '@squidcloud/client';
import * as readline from 'readline';
import * as crypto from 'crypto';
const rl = ...
function main() {
const squid = ...
readInput(squid);
}
function readInput(squid: Squid) {
setTimeout(() => {
rl.question('Usage [exit / insert / read / update / delete]: ', (input) => {
if (input.toLowerCase() === 'exit') {
rl.close();
process.exit(0);
} else if (input.toLowerCase() === 'insert') {
rl.question('Name: ', (name) => {
insertUser(name, squid);
readInput(squid);
})
} else if (input.toLowerCase() === 'read') {
readUsers(squid);
readInput(squid);
} else if (input.toLowerCase() === 'update') {
rl.question('Id: ', (id) => {
rl.question('Name: ', (name) => {
rl.question('Age: ', (age) => {
updateUser(squid, id, name, parseInt(age));
readInput(squid);
})
})
})
} else if (input.toLowerCase() === 'delete') {
rl.question('Id: ', (id) => {
deleteUser(squid, id);
readInput(squid);
})
} else {
console.log("Please select a recognized command");
readInput(squid);
}
})
}, 1500)
}
function insertUser(name: string, squid: Squid) {
...
}
function readUsers(squid: Squid) {
...
}
function updateUser(squid: Squid, id: string, name?: string, age?: number) {
...
}
function deleteUser(squid: Squid, id: string) {
...
}
main();
Next steps
Congratulations on completing the tutorial! To learn about key features of the Squid backend including security functionality, view the Backend SDK documentation. To learn about the functionality of the Squid Client SDK including more complex data queries, managing secrets, and interacting with your storage buckets, view the Client SDK documentation.
The Project
This guide explains how to create a simple Angular application that utilizes the Squid Angular SDK with a Squid backend. The application inserts random users into the database and display their information on an Angular website.
Insert
Squid organizes data into collections and documents. Documents are individual records in the database (such as rows in a table), while collections are groups of documents (similar to unstructured tables). To access a Squid collection, use the collection()
method with the collection name as the first parameter.
To create a new document in Angular, use the insert()
method on a document reference. Document references can be initialized with a document ID, which you can learn more about in our documentation. Create a new insert-user
component:
cd my-app-frontend
ng generate component insert-user
This will create a new insert-user
directory at src/app/insert-user
. Inside that directory is a insert-user.component.ts
file where we can update the components appearance and functionality. Add a <button>
element that creates a new user in the users
collection every time the button is clicked:
import { Component } from '@angular/core';
import { Squid } from '@squidcloud/client';
// Define your type
type User = { id: string; email: string; age: number };
@Component({
selector: 'insert-user',
template: `
<button (click)="insertNewUser()">Create user</button>
<br />
`,
})
export class InsertUserComponent {
constructor(private readonly squid: Squid) {}
// Insert data
async insertNewUser(): Promise<void> {
const userId = crypto.randomUUID();
const email = `${userId}@gmail.com`;
await this.squid
.collection<User>('users')
.doc(userId)
.insert({
id: userId,
email,
age: Math.ceil(Math.random() * 100),
});
}
}
Read
Next, create a read-users
component to display the list of users on the webpage.
ng generate component read-users
Squid provides a robust and powerful query system. To simply get the collection of users from Squid, there are multiple different options which suit different use cases. In src/app/read-users/read-users.component.ts
, we can subscribe to the users
collection by calling the snapshots()
method:
import { Component } from '@angular/core';
import { Squid } from '@squidcloud/client';
import { map } from 'rxjs';
type User = { id: string; email: string; age: number };
@Component({
selector: 'read-users',
template: ` <ul>
<li *ngFor="let user of users | async">
{{ user.email }} - {{ user.age }}
</li>
</ul>`,
})
export class ReadUsersComponent {
constructor(private readonly squid: Squid) {}
users = this.squid
.collection<User>('users')
.query()
.dereference()
.snapshots();
}
By subscribing to the users
collection, the list of users displayed will update in real-time whenever a new user is added to the collection.
To see this functionality in action, include the new components in the src/app/app.component.html
file:
<insert-user />
<read-users />
<router-outlet></router-outlet>
With Squid's real-time functionality, you can insert data in one tab and instantly see the updates in other tabs without requiring any additional code!
Update
To update a document in the collection, call the update()
method on a DocumentReference
and pass in an object that contains the partial update data as an argument. To do so, we will first create a new update-user
component:
ng generate component update-user
Next, update src/app/update-user/update-user.component.ts
with an HTML form that updates a user's age in the database. To use the form, first update my-app-frontend/src/app/app.module.ts
to import the FormsModule
:
import { FormsModule } from "@angular/forms";
...
@NgModule({
imports: [
FormsModule,
...
],
})
Now that we can use the FormsModule
, update src/app/update-user/update-user.component.ts
with a new form. The form also contains some basic styling:
import { Component } from '@angular/core';
import { Squid } from '@squidcloud/client';
type User = { id: string; email: string; age: number };
@Component({
selector: 'update-user',
template: `
<form style="margin: 10px" name="form" (ngSubmit)="updateUser()">
<div>
<label style="padding-right: 9px" for="input-id">ID: </label>
<input id="input-id" name="input-id" [(ngModel)]="inputId" />
</div>
<div>
<label for="input-age">Age: </label>
<input
id="input-age"
name="input-age"
type="number"
[(ngModel)]="inputAge"
/>
</div>
<button style="margin-top: 4px">Update Age</button>
</form>
`,
})
export class UpdateUserComponent {
constructor(private readonly squid: Squid) {
this.inputId = '';
}
inputId: string;
inputAge: number | undefined;
async updateUser(): Promise<void> {
if (this.inputAge) {
await this.squid.collection<User>('users').doc(this.inputId).update({
age: this.inputAge,
});
}
}
}
We can then update src/app/app.component.html
to include the new component. Remember that the email prefixes on the webpage are actually user IDs. To use the update form, enter a user ID along with a new age:
<insert-user />
<read-users />
<update-user />
<router-outlet></router-outlet>
Delete
To delete a document, call the delete()
method on a DocumentReference
. To do so, we will first create a new delete-user
component:
ng generate component delete-user
Next, we will implement another form in src/app/delete-user/delete-user.component.ts
where we can input the ID of the user we want to delete.
import { Component } from '@angular/core';
import { Squid } from '@squidcloud/client';
type User = { id: string; email: string; age: number };
@Component({
selector: 'delete-user',
template: `
<form style="margin: 10px" name="form" (ngSubmit)="deleteUser()">
<div>
<label style="padding-right: 9px" for="input-id">ID: </label>
<input id="input-id" name="input-id" [(ngModel)]="inputId" />
</div>
<button style="margin-top: 4px">Delete</button>
</form>
`,
})
export class DeleteUserComponent {
constructor(private readonly squid: Squid) {
this.inputId = '';
}
inputId: string;
async deleteUser(): Promise<void> {
await this.squid.collection<User>('users').doc(this.inputId).delete();
}
}
Finally, bring the delete-user
component into src/app/app.component.html
to test out the new delete functionality:
<insert-user />
<read-users />
<update-user />
<delete-user />
<router-outlet></router-outlet>
After completing the delete component, we are now able to use Squid’s built in database service to accomplish all the major functionality that is required in a database.
Next steps
Congratulations on completing the tutorial! To learn about key features of the Squid backend including security functionality, view the Backend SDK documentation. To learn about the functionality of the Squid Client SDK including more complex data queries, managing secrets, and interacting with your storage buckets, view the Client SDK documentation.