null
support for whereIn
Previously a query containing null
inside a whereIn
condition ran the following SQL query:
// Get all posts without a tagId or with tag 3fc747c1-3140-4993-a5ef-fe13983efab9
query('posts').whereIn('tagId', [ null, '3fc747c1-3140-4993-a5ef-fe13983efab9' ])
// SQL:
// SELECT * FROM posts WHERE tagId IN (null, '3fc747c1-3140-4993-a5ef-fe13983efab9')
The problem is that in SQL null IN (null)
is false
. Null values need to checked using the IS NULL
operator. So the above query would not return any post with tagId == null
.
With this update we've changed the SQL generation to work like expected, the following SQL is now executed with the above query:
// Get all posts without a tagId or with tag 3fc747c1-3140-4993-a5ef-fe13983efab9
useQuery(query('posts').whereIn('tagId', [ null, '3fc747c1-3140-4993-a5ef-fe13983efab9' ]))
// SQL:
// SELECT * FROM posts WHERE tagId IN ('3fc747c1-3140-4993-a5ef-fe13983efab9') OR tagId IS NULL
Previously enums have been typed as a string
in TypeScript. This makes them type unsafe and prevents autocompletion from working.
With todays update enum fields have a more specific type:
To use the improved types, update the generated TypeScript types in the Types section of your project.
whereIn
FilterIt's now possible to queries like WHERE some_field IN (1, 2, 3)
using the new whereIn
call:
Here's how to use it:
function Tasks() {
const tasks = useQuery(query('tasks').whereIn('status', ['pending', 'completed']));
// ...
}
This can also be useful when fetching relations:
function Task({ task }) {
// Get 10 tasks
const tasks = useQuery(query('tasks').limit(10));
// Get comments related to the tasks
const taskIds = tasks ? tasks.map(task => task.id) : []
const taskComments = useQuery(query('task_comments').whereIn('taskId', taskIds))
// ...
}
To use this new function, update the thin-backend
package in your app and update the generated TypeScript types.
Previously it was not possible to query your Thin backend from Node.js applications. Node.js doesn't support WebSockets out of the box. Thin is using WebSockets are the primary communication layer to the Thin Backend, therefore Node.js based apps always failed to connect.
We've now internally added support for querying Thin via HTTP instead of just via WebSockets. The thin-backend
package will automatically detect when a WebSocket is not available and then fall back to use a fetch(..)
call. Of course this doesn't support live queries, but we don't want those on the node.js servers anyways.
Here's how a Thin query can look from a nextjs API route:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import { initThinBackend, query } from 'thin-backend'
import { DataSyncController } from 'thin-backend';
type Data = {
name: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
initThinBackend({ host: process.env.NEXT_PUBLIC_BACKEND_URL });
const task = await query('tasks').fetchOne();
res.status(200).json({ task })
}
Serverside Auth:
The query('tasks').fetchOne()
query will run as an anonymous / not logged in user. If you want to access the Thin api as a logged in user, you need to pass the JWT from the client to the next.js app.
On the client side you can retrieve the JWT like this:
import { DataSyncController } from 'thin-backend';
const jwt = DataSyncController.getJWT();
fetch("/api/my-nextjs-endpoint", { headers: { Authentication: jwt } })
On the serverside you need to override the getJWT
function of Thin:
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import { initThinBackend, query } from 'thin-backend'
import { DataSyncController } from 'thin-backend';
type Data = {
name: string
}
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
initThinBackend({ host: process.env.NEXT_PUBLIC_BACKEND_URL });
DataSyncController.getJWT = () => req.headers.Authentication;
const task = await query('tasks').fetchOne();
res.status(200).json({ task })
}
If you use Thin with Node.js, update to the latest version of the thin-backend
package.
We've added some functions that already exist in the Thin React integration to the thin-backend-vue
package:
useCurrentUserId
useIsLoggedIn
useCurrentUser
If you use Thin with Vue, update to the latest version of thin-backend-vue
to use these new functions.
The JWT private and public keys are now visible in the project setting. This helps when integrating Thin with other API systems that need to verify the auth state.
.where({ someField: someValue })
causing an error because it was not correctly encoded.
ALL
SQL Keyword is now correctly escaped when using it as an identifier in the Schema Designer.
Non-exhaustive patterns in Just closeSignalMVar
error message in the console. That error was caused by this issue.
users
table. It also prevents you from deleting built-in columns on the users
table.
The Thin + Vercel Integration is now available on the Vercel Marketplace. With the Vercel Integration you can connect your Vercel projects to a Thin Backend with just a few clicks:
The integration automatically sets the BACKEND_URL
environment variable on your Vercel project. This environment variable is used by commonly used Thin project templates to connect to the backend server.
Previously live queries didn't remove database records when they did't fit the query conditions anymore after an update operation.
E.g. in the code below a task is not removed from the pendingTasks
array, even when updateRecord('tasks', task.id, { isCompleted: true })
is executed:
function Tasks() {
const pendingTasks = useQuery(query('tasks').where('isCompleted', false));
const completeTask = task => {
// In old versions this didn't remove the task from `pendingTasks`
// even though there's condition `.where('isCompleted', false)`
updateRecord('tasks', task.id, { isCompleted: true });
};
return pendingTasks?.map(task => <div>
{task.title}
<button onClick={() => completeTask(task)}>complete</button>
</div>)
}
With the latest version of Thin, update operations are checked against the query conditions. Records are removed from the result set when they don't match anymore:
function Tasks() {
const pendingTasks = useQuery(query('tasks').where('isCompleted', false));
const completeTask = task => {
// With the latest Thin version, this will
// automatically remove the task from `pendingTasks`
updateRecord('tasks', task.id, { isCompleted: true });
};
return pendingTasks?.map(task => <div>
{task.title}
<button onClick={() => completeTask(task)}>complete</button>
</div>)
}
Whenever a new database record is created using createRecord(..)
, Thin appends them to the result list of all useQuery(..)
results.
When the useQuery(..)
is sorted by a createdAt
column, newest records first, it will prepend the result instead:
// New Tasks appear at the end of the list
const tasks = useQuery(query('tasks'));
With the new Thin version, the order by direction is taken into account to decide whether to append or prepend a new database record:
// New tasks appears at the end
const completedTasksFirst = useQuery(query('tasks').orderByAsc('isCompleted'));
// New task appears at the start of the list
const completedTasksLast = useQuery(query('tasks').orderBy('isCompleted'));
It's now also possible to override the placement of new records:
import { NewRecordBehaviour } from 'thin-backend';
const completedTasksFirst = useQuery(query('tasks').orderBy('isCompleted'), {
newRecordBehaviour: NewRecordBehaviour.APPEND_NEW_RECORD // or PREPEND_NEW_RECORD
});
CREATE OR REPLACE
when updating a function in a migration. Previously the migration generator tried to delete a function, and then re-create it. The DELETE FUNCTION
often fails because the function is still in use somewhere.
You can now run Thin Backend on your own infrastructure. Whether you want to code on a plane or deploy Thin on your own AWS infrastructure, you can use our new docker image to deploy Thin in a few minutes.
The database schema and your migrations are stored in .sql
files, so you can easily put them into your project's git repo.
You can find install instructions in the documentation.
We've added a new Thin community forum. This will replace the Thin Slack channel going forward. The forum has the advantage that other people can later find solutions on Google.
Check it out at community.thin.dev.
public
, e.g. public_messsages
.
SET
or SELECT
statements appears in the database schema. This typically happend when importing an existing database schema into Thin.
Login with Email
or Sign up with Email
, so you can get back to the GitHub login process
We want to make it even easier to get started with Thin. So we've redesigned the process of connecting your first frontend to Thin.
The Frontend button in the navigation now always displays instructions on how to set up your react frontend with Thin. Previously clicking the Frontend button opened the connected Single Page App.
We've also simplified the setup process by removing all options that might not be useful for the first start. Other project templates like the Next.js Template are still accessible from the sidebar.
If you've signed up via email and forgot your password, you can now reset it yourself, by clicking Forgot Password
on the login screen.
&
sign in the url. The new url format has no &
anymore. For b.c. old urls are still working.
requireLogin
is not set
We've finished a new react component for dealing with logins and signups right from within your app. This will avoid all the redirects that happen through the login process, improve latency and allow for easier customization. You can find a demo here.
Right now this is only available for react users. We will port this to other frameworks in the future as well.
To use the new login:
npm update thin-backend
# Install the new react package
npm install thin-backend-react
Inside your app replace all:
// OLD:
import .. from 'thin-backend/react'
// NEW:
import .. from 'thin-backend-react'
Now when you're using <ThinBackend requireLogin>
, the login will happen without a redirect, right from within your app.
You can render the login form manually like this:
import { LoginAndSignUp } from 'thin-backend-react/auth-ui';
<LoginAndSignUp />
To increase awareness about the Thin Components, we've added a first draft of the new Components section inside the app. We will extend this over the coming weeks.
Based on feedback we've received last week, we've worked on improving the page load time of the Thin website.
Next to the performance improvements we've also added a few testimonials and replaced the demo video at the top with a code example.
BACKEND_URL
SetupWhen setting up a Thin project for the first time using one of our project templates, it can happen that you miss to set the BACKEND_URL
environment variable.
The error message that you'll receive in that case now contains a link to the new Troubleshooting Documentation that helps you to fix this issue.
Additionally the Backend URL is now visible in the header of the Schema Designer and other parts of the app. This makes it easier to find.
This has been requested quite a few times. You can now use Thin with your Vue projects 🎉
<script setup lang="ts">
import { createRecord, query, updateRecord, type Task } from 'thin-backend';
import { useQuery } from 'thin-backend-vue';
const tasks = useQuery(query('tasks').orderBy('createdAt'));
function updateTask(task: Task) {
updateRecord('tasks', task.id, { title: window.prompt('New title') || '' })
}
function addTask() {
createRecord('tasks', {
title: window.prompt('Title:') || ''
});
}
</script>
<template>
<div v-for="task in tasks" v-on:dblclick="updateTask(task)">
{{task.title}}
</div>
<button v-on:click="addTask()">Add Task</button>
</template>
The general approach in Vue is very similiar to how we have it working with react. E.g. the react hook useQuery
has a Vue pendant called useQuery
.
Check out the basic documentation on GitHub
Based on feedback we've updated the Thin Pricing to make it more intuitive:
You can now specify the redirect url where a user is redirected after logout:
logout({
redirect: "https://example.com/logout-completed"
})
Based on user feedback we've added support for using your own Postgres database with Thin. This makes it easier to adopt Thin Backend with existing projects.
Right now this feature is hidden behind a feature flag and only available to selected users.
If you're curious, check out the docs here.
If you want to use Thin with your existing database, reach out and we'll help you get up and running.
project_id
not being correctly styled
QueryBuilder.subscribe
. This improves the developer experience for people working with Svelte
We've improved the Vercel integration, so you can now create new projects with Vercel even faster than before.
There's now two ways to connect your Thin app with Vercel:
New Projects: Start inside Thin, create a new Thin project and follow the frontend setup instructions. When you're prompted, selected that you want to use Vercel for hosting. This will redirect you to Vercel.
This already worked previously, but you had to manually specify the BACKEND_URL
before the first deployment in Vercel. With the new Integration, this is taken care of. So you don't need to specifiy anything.
Existing Vercel Projects: Open the Thin Integration on the Vercel Integrations Marketplace, click "Add Integration" and follow the instructions on the screen.
The Thin JavaScript client now caches results of useQuery(..)
calls in-memory. This improves the user-experience when using a routing library and going back in page history.
useIsConnected()
hookThe new useIsConnected()
react hook returns true
when the browser is online and connected to the Thin Backend server. When the client is offline, it returns false
.
IHP Backend was always just a working title. We've now settled on a new name: Thin Backend.
The idea behind the name is simple: With Thin you have a thin backend layer, and a rich frontend layer in your app.
We've also moved on from the previous domains ihpbackend.digitallyinduced.com and thinbackend.app to thin.dev now. Short and fresh! Give it a try at thin.dev if you're curious.
With the new name we've also renamed the NPM package ihp-backend
to thin-backend
. If you have an existing thin app, go to the package.json and replace ihp-backend
with thin-backend
in there.
The domain at which your app is reachable is appname.thinbackend.app now. While di1337.com is a cool domain, it's now time to have something more rememberable.
The Thin infrastructure has been completely rebuild to make sure that everything always works. Previously, every Thin project was running inside a single docker container. This turned out to be pretty inefficient, expensive and error-prone. We've now refactored everything to run without docker and in a more efficient way. Since this refactoring everything has been running much more smooth, stable and fast.
We've moved the server hosting region from germany over to us-east-1 in AWS. This will deliver better average latency for everyone. We'll very soon add more regions to make things even faster.
Thin Backend is now a product offered by digitally induced, Inc., a corporation registered in Delaware, US, a wholly owned subsidiary of digitally induced GmbH, registered in Germany.
Build your apps faster with ready-to-use customizable components. With Thin Components you can e.g. add a simple CRUD table to your app with just a single line of code.
When rendered, this Crud component can look like this:
The components are open source and you can contribute to it on the thin GitHub repo :)
Additionally we also have a still-WIP landing page for the components.
Soon we'll add more components, like ComboBox/ or Upload/. In the future we might even add a <StripePayment /> component and much more to deal with high level functionality in your thin apps.
When you're too far away from an IHP Backend server (like e.g. using an IHP Backend app from the US) it previously felt a bit slow. This is because of the increased latency.
We've just added Optimistic Updates. Now when a database record is created using createRecord
, it will instantly appear in your apps UI even though the server might have not responded yet. This will improve the user experience for any operations after the initial data fetch.
Optimistic Updates are enabled by default. They work with createRecord
, updateRecord
and deleteRecord
operations.
Make sure to use the latest version of the ihp-backend
npm package by running npm update
to make use of optimistic updates.
We've added a new ReScript Template. So if you're using ReScript, you can now start a new project using IHP Backend with a few clicks.
There's also a ReScript Example App. If you're curious how IHP Backend + ReScript looks in practise, check it out on GitHub.
You can now build search functionality like the above in few lines of code:
function ProductSearch() {
const [searchQuery, setSearchQuery] = useState('');
const onChange = useCallback(event => setSearchQuery(event.target.value), [ setSearchQuery ]);
const products = useQuery(query('products').whereTextSearchStartsWith('textSearch', searchQuery));
return <form>
<input type="text" className="form-control" value={searchQuery} onChange={onChange}/>
<div>
Result: {products?.map(product => <div key={product.id}>{product.name}</div>)}
</div>
</form>
}
You can find more details on this in the new Search Guide.
If you don't need a project anymore, you can now delete it from the project settings:
When you have policies for a specific action (like e.g. DELETE
operations), they're now shown like this:
For the "Users can delete their messages" policy, it now says "DELETE allowed if".
We've added a new example app to the start page. The example app is a simple chat app. You can try it out here.
You can find the source code on GitHub.
loginWithRedirect
http://localhost:3000
by default now. Previously an app crashed when this was not configured before logging in the first time.
useQuery
etc. not correctly restoring after the internet connection was lost for a moment.
updated_at
You can now add updated_at
timestamps to your tables with a single click:
When adding the updated_at
from the suggested columns, IHP Backend will automatically wire up a database trigger that updates the timestamp whenever the row is updated. So you don't need to manually update the updated_at
timestamp.
This week we did some improvements to make it easier to check whether a user is already logged in or not, and to display different content based on that.
Here's an example of using the new useIsLoggedIn()
react hook to display a "Hello" message or a "Login required" depending on the login state:
import { useIsLoggedIn, useCurrentUser } from 'ihp-backend/react';
function Content() {
const isLoggedIn = useIsLoggedIn();
if (isLoggedIn === null) {
return <div>Loading ...</div>
}
if (isLoggedIn) {
return <LoggedInContent />
} else {
return <LoggedOutContent />
}
}
function LoggedInContent() {
const user = useCurrentUser();
return <div>Hello {user.name}!</div>
}
function LoggedOutContent() {
return <div>You are not logged in yet</div>
}
Check out the docs for more details on this.
We've added new pages to describe all different features and use cases of IHP Backend:
We've added support for OAuth Providers this week. You can now quickly enable "Login with Google" functionality in your project settings.
This is how the setup looks in the project settings:
Enabling google login automatically generates and runs a migration that adds a google_user_id
column to your users table.
Learn how to enable "Login with Google" in the docs.
As several people already asked for details on the future pricing plans we've now added a first iteration of the pricing.
The pricing might still change in the coming weeks as we're still collecting feedback on this. Reach out if you have any input on this :)
Next to the TypeScript types, IHP Backend now also provides you with ReScript Types. Like with the existing TypeScript definitions, you only need to install the generated npm module. You can find the install instructions in the "Type Definitions" tab of your project.
Here's an example of a ReScript Todo App:
Next week we'll add a rescript template as well, so it's even easier to start your project with ReScript.
created_at
columns now get an index by default when added to a table
useQuerySingleResult
(like useQuery, but only returns a single database record)
With the new <IHPBackend/>
Component it's easier to use IHP Backend in your project. It replaces the previous ensureIsUser().then
pattern:
The new API also renders earlier. The previous ensureIsUser()
pattern always needed to complete the login process before showing the react application. With the new component based approach loading spinners and other components will already be visible until the login is completed.
To use the new API, update to v0.3 of the ihp-backend
package by running npm update
in your project. The "old" way with ensureIsUser still works, but it's highly recommended to switch to the new API.
Check the API Reference for more details.
We've added a new Guide describing common use cases around auth, like how to make a login and logout button.
The weekly change log is now also available on the website.
npm run start
=> npm run dev
We've renamed the npm run start
command to npm run dev
in all project templates to be more consistent with how Next.js does things.
We've added updateRecords
and deleteRecords
so you can update and delete multiple database records in a single database operation:
You can now wrap multiple database operations in a database transaction to make sure things don't get out of sync:
Learn more about transactions in the docs.
Simplified the documentation navigation: The Home tab has been removed, it's now Guides and API Reference only
Several code snippets in the docs now have a click to copy behaviour
Changed the logo size in the navigation bar
The logs view in the project settings now only shows up to 512 lines of logs.
We've added a list of supported database types to the Database Guide
Several mobile improvements to the start page
Fixed cache busting sometimes not working as expected
Internal Performance Improvements:
Previously an operation like createRecord
needed multiple SQL calls internally. Now they only need one call to the database. This saves 2ms of latency on every operation.
useQuery
without LoginIt's now possible to call useQuery
and other query functions without requiring the user to be logged in. Previously trying to access the database while being logged out triggered an error.
Tables are of course still protected by policies. To e.g. allow read-only access to logged out users, you need to define a policy like this:
The location of the ihp-backend has moved from a custom .tar.gz file at https://ihpbackend.digitallyinduced.com/ihp-backend.tar.gz to a normal npm module. Additionally the "ihp-datasync" module is now re-exported from the ihp-backend package, so instead of two dependencies, you only need a single one now.
To keep using the latest version of the JS SDK, change your package.json like this:
Our previous demo video was already a bit outdated as a lot of things have improved since early january. You can find the new demo video here.
You can now upload your own App Icon visible on the Login Screen of your App.
To change the Icon, open the project settings and click on Auth:
If you're a Svelte Fan this is good news! You can check out the Svelte Guide in the Docs.
We've previously already supported Next.js by using the Next.js template. Additionally we now have documentation on how to use IHP Backend with Next.js when not using that template project.
Previously backend domains were in the format random string.di1337.com. When working with multiple projects in can be confusing what Backend URL points to what project again. To fix this new projects now have domains in the format project name.di1337.com.
Here's a before (left) and after (right):
query("tables").limit(100)
not working
useCurrentUser
function has moved to better support expo. Update you imports from import { useCurrentUser } from 'ihp-backend'
to import { useCurrentUser } from 'ihp-backend/react'
useCurrentUser
function is now implemented in terms of useQuery
, so it will automatically update when the user record has been changed (e.g. the user profile picture was changed). This also saves one HTTP request and lowers latency.
user_id
column to a table in the Schema Designer, the default value is now set to ihp_user_id()
by default. This means that code like createRecord('tasks', { title: 'Hello World', userId: getCurrentUserId() })
can now be written as createRecord('tasks', { title: 'Hello World' })
.
query("table").filterWhere("someCol", null)
useQuery
now take the ORDER BY into account when deciding whether a newly added record should be appended or prepended to the result set. E.g. if you display the latest 10 tasks (latest first), useQuery
will now add new tasks at the beginning of the result set instead of adding it to the end.