Retravailler la feuille de chaleur
Aperçu général
Retool est une plate-forme à faible code pour construire rapidement des outils internes. Il permet aux développeurs de créer des applications personnalisées en se connectant aux bases de données, aux API et aux services grâce à une interface glisser-déposer combinée avec le code JavaScript, permettant le développement rapide des panneaux d'administration, des tableaux de bord et des applications commerciales.
C'est pas vrai. Note: Niveau gratuit disponible pour jusqu'à 5 utilisateurs. Les plans payés commencent à 10 $/utilisateur/mois pour les équipes.
Commencer
Configuration du compte
# Sign up process:
# 1. Visit retool.com
# 2. Create account with email or Google/GitHub
# 3. Choose organization name
# 4. Set up first application
# 5. Connect data sources
# Organization setup:
# - Configure SSO (optional)
# - Set up user groups and permissions
# - Configure environment variables
# - Set up version control
# - Configure deployment settings
Première demande
# Create new app:
# 1. Click "Create new" → "App"
# 2. Choose template or start blank
# 3. Name your application
# 4. Select starting components
# 5. Configure data connections
# Basic app structure:
# - Components: UI elements (tables, forms, buttons)
# - Queries: Data operations (API calls, database queries)
# - JavaScript: Custom logic and transformations
# - State: Application data and variables
```_
### Aperçu de l'interface
```bash
# Main interface areas:
# - Component Library: Drag-and-drop UI elements
# - Canvas: Visual app builder
# - Query Editor: Data operations panel
# - State Inspector: Variable and data viewer
# - Code Editor: JavaScript and SQL editor
# - Resource Manager: Data source connections
```_
## Composantes
### Composants d'affichage des données
```bash
# Table component:
# - Display tabular data
# - Sorting and filtering
# - Pagination
# - Row selection
# - Inline editing
# - Custom columns
# Table configuration:
table1.data = {{ query1.data }}
table1.columns = [
{
"id": "name",
"label": "Name",
"type": "string"
},
{
"id": "email",
"label": "Email",
"type": "email"
}
]
Composantes d'entrée
# Text Input:
textInput1.value # Get current value
textInput1.setValue("text") # Set value programmatically
# Select component:
select1.value # Selected value
select1.selectedItem # Selected item object
select1.data = {{ query1.data }}
select1.valueKey = "id"
select1.labelKey = "name"
# Date Picker:
datePicker1.value # Selected date
datePicker1.formattedValue # Formatted date string
Composantes d'action
# Button component:
button1.click() # Trigger click programmatically
button1.loading = {{ query1.isFetching }}
button1.disabled = {{ !form1.isValid }}
# Button event handlers:
// onClick event
if (textInput1.value) {
query1.trigger();
} else {
utils.showNotification({
title: "Error",
description: "Please enter a value",
notificationType: "error"
});
}
Composantes de mise en page
# Container:
# - Group related components
# - Apply consistent styling
# - Control visibility and layout
# Tabs:
# - Organize content into sections
# - Dynamic tab creation
# - Conditional tab visibility
# Modal:
modal1.open() # Open modal
modal1.close() # Close modal
modal1.opened # Check if open
Sources des données
Connexions aux bases de données
# Supported databases:
# - PostgreSQL
# - MySQL
# - MongoDB
# - Redis
# - Elasticsearch
# - BigQuery
# - Snowflake
# - Microsoft SQL Server
# Connection setup:
# 1. Go to Resources tab
# 2. Click "Create new resource"
# 3. Select database type
# 4. Enter connection details
# 5. Test connection
# 6. Save resource
Intégration de l'API REST
# REST API resource:
# 1. Create new REST API resource
# 2. Set base URL
# 3. Configure authentication
# 4. Add headers if needed
# 5. Test connection
# API query example:
// GET request
return await api1.run({
url: "/users",
method: "GET",
params: {
page: table1.pageIndex + 1,
limit: table1.pageSize
}
});
// POST request
return await api1.run({
url: "/users",
method: "POST",
body: {
name: textInput1.value,
email: textInput2.value
}
});
Intégration de GraphQL
# GraphQL setup:
# 1. Create GraphQL resource
# 2. Set endpoint URL
# 3. Configure authentication
# 4. Add headers if needed
# GraphQL query:
query GetUsers($limit: Int!) {
users(limit: $limit) {
id
name
email
createdAt
}
}
# Variables:
{
"limit": {{ table1.pageSize }}
}
Enquêtes et transformateurs
Questions SQL
-- Basic SELECT query
SELECT * FROM users
WHERE created_at >= {{ datePicker1.value }}
ORDER BY created_at DESC
LIMIT {{ table1.pageSize }}
OFFSET {{ table1.pageIndex * table1.pageSize }};
-- Parameterized query
SELECT * FROM orders
WHERE user_id = {{ select1.value }}
AND status = {{ textInput1.value || 'active' }}
AND created_at BETWEEN {{ startDate.value }} AND {{ endDate.value }};
-- Insert query
INSERT INTO users (name, email, department)
VALUES (
{{ textInput1.value }},
{{ textInput2.value }},
{{ select1.value }}
);
Transformateurs JavaScript
// Transform API response
const transformedData = query1.data.map(item => ({
id: item.id,
fullName: `${item.firstName} ${item.lastName}`,
email: item.email,
status: item.isActive ? 'Active' : 'Inactive',
joinDate: new Date(item.createdAt).toLocaleDateString()
}));
return transformedData;
// Aggregate data
const summary = query1.data.reduce((acc, item) => {
acc.totalRevenue += item.revenue;
acc.totalOrders += 1;
acc.averageOrderValue = acc.totalRevenue / acc.totalOrders;
return acc;
}, { totalRevenue: 0, totalOrders: 0, averageOrderValue: 0 });
return summary;
Enquête en chaîne
// Sequential queries
const userData = await query1.trigger();
const userOrders = await query2.trigger({
additionalScope: {
userId: userData.id
}
});
return {
user: userData,
orders: userOrders
};
// Parallel queries
const [users, orders, products] = await Promise.all([
query1.trigger(),
query2.trigger(),
query3.trigger()
]);
return { users, orders, products };
JavaScript dans Retool
Fonctions mondiales
// Utility functions
utils.showNotification({
title: "Success",
description: "Record saved successfully",
notificationType: "success",
duration: 3000
});
utils.openUrl("https://example.com", { newTab: true });
utils.copyToClipboard(textInput1.value);
utils.downloadPage("report.pdf");
utils.goToApp("app-uuid", {
userId: select1.value
});
Administration de l ' État
// Set temporary state
tempState.setValue({
selectedUser: table1.selectedRow.data,
editMode: true,
originalData: { ...table1.selectedRow.data }
});
// Get state values
const currentUser = tempState.value.selectedUser;
const isEditing = tempState.value.editMode;
// Clear state
tempState.setValue({});
// Local storage
localStorage.setValue("userPreferences", {
theme: "dark",
language: "en"
});
const preferences = localStorage.getValue("userPreferences");
Gestion des événements
// Component event handlers
// Button onClick
async function handleSubmit() {
try {
button1.setLoading(true);
const result = await query1.trigger();
utils.showNotification({
title: "Success",
description: "Data saved successfully"
});
modal1.close();
table1.refresh();
} catch (error) {
utils.showNotification({
title: "Error",
description: error.message,
notificationType: "error"
});
} finally {
button1.setLoading(false);
}
}
// Table row selection
function handleRowSelect() {
const selectedData = table1.selectedRow.data;
textInput1.setValue(selectedData.name);
textInput2.setValue(selectedData.email);
select1.setValue(selectedData.department);
}
Caractéristiques avancées
Composants personnalisés
// Custom React component
const CustomChart = ({ data, title }) => {
const chartData = data.map(item => ({
name: item.month,
value: item.revenue
}));
return (
<div>
<h3>{title}</h3>
<ResponsiveContainer width="100%" height={300}>
<LineChart data={chartData}>
<XAxis dataKey="name" />
<YAxis />
<CartesianGrid strokeDasharray="3 3" />
<Line type="monotone" dataKey="value" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
</div>
);
};
// Use custom component
<CustomChart
data={query1.data}
title="Monthly Revenue"
/>
Flux de travail et automatisation
// Workflow example: User onboarding
async function onboardNewUser() {
try {
// Step 1: Create user account
const user = await createUserQuery.trigger({
additionalScope: {
name: nameInput.value,
email: emailInput.value,
department: departmentSelect.value
}
});
// Step 2: Send welcome email
await sendEmailQuery.trigger({
additionalScope: {
to: user.email,
template: "welcome",
variables: { name: user.name }
}
});
// Step 3: Create default permissions
await createPermissionsQuery.trigger({
additionalScope: {
userId: user.id,
role: "employee"
}
});
// Step 4: Add to team
await addToTeamQuery.trigger({
additionalScope: {
userId: user.id,
teamId: teamSelect.value
}
});
utils.showNotification({
title: "Success",
description: "User onboarded successfully"
});
} catch (error) {
console.error("Onboarding failed:", error);
utils.showNotification({
title: "Error",
description: "Failed to onboard user",
notificationType: "error"
});
}
}
Autorisations et sécurité
// Role-based access control
const userRole = current_user.groups[0];
// Hide components based on role
if (userRole !== 'admin') {
deleteButton.hidden = true;
editButton.hidden = true;
}
// Conditional queries
if (userRole === 'admin') {
return await adminQuery.trigger();
} else {
return await userQuery.trigger({
additionalScope: {
userId: current_user.id
}
});
}
// Data filtering by user
SELECT * FROM orders
WHERE user_id = {{ current_user.id }}
OR {{ current_user.groups.includes('admin') }}
Réceptivité mobile
Conception sensible
// Screen size detection
const isMobile = window.innerWidth < 768;
const isTablet = window.innerWidth >= 768 && window.innerWidth < 1024;
const isDesktop = window.innerWidth >= 1024;
// Conditional styling
if (isMobile) {
container1.style = {
padding: "10px",
flexDirection: "column"
};
} else {
container1.style = {
padding: "20px",
flexDirection: "row"
};
}
Composants mobiles spécifiques
// Mobile navigation
const MobileNav = () => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="mobile-nav">
<button onClick={() => setIsOpen(!isOpen)}>
Menu
</button>
{isOpen && (
<div className="nav-menu">
<a href="#dashboard">Dashboard</a>
<a href="#users">Users</a>
<a href="#settings">Settings</a>
</div>
)}
</div>
);
};
Déploiement et environnement
Gestion de l ' environnement
# Environment types:
# - Development: Testing and development
# - Staging: Pre-production testing
# - Production: Live application
# Environment variables:
# {{ retool.environment }} - Current environment
# {{ retool.configVars.API_URL }} - Environment-specific config
Contrôle de version
# Git integration:
# 1. Connect to GitHub/GitLab repository
# 2. Configure branch protection
# 3. Set up pull request workflow
# 4. Enable automatic deployments
# Branching strategy:
# - main: Production code
# - staging: Pre-production testing
# - feature/*: Feature development
# - hotfix/*: Critical bug fixes
Gestion des libérations
// Release checklist automation
const releaseChecklist = [
{ task: "Run tests", completed: false },
{ task: "Update documentation", completed: false },
{ task: "Review security", completed: false },
{ task: "Performance testing", completed: false },
{ task: "Stakeholder approval", completed: false }
];
// Deployment script
async function deployToProduction() {
const incompleteItems = releaseChecklist.filter(item => !item.completed);
if (incompleteItems.length > 0) {
utils.showNotification({
title: "Deployment Blocked",
description: `Complete: ${incompleteItems.map(i => i.task).join(', ')}`,
notificationType: "warning"
});
return;
}
// Proceed with deployment
await deployQuery.trigger();
}
Optimisation des performances
Optimisation des requêtes
// Debounced search
let searchTimeout;
function handleSearchInput() {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchQuery.trigger({
additionalScope: {
searchTerm: searchInput.value
}
});
}, 500);
}
// Cached queries
const cachedData = localStorage.getValue("userList");
const cacheExpiry = localStorage.getValue("userListExpiry");
if (cachedData && cacheExpiry > Date.now()) {
return cachedData;
} else {
const freshData = await userQuery.trigger();
localStorage.setValue("userList", freshData);
localStorage.setValue("userListExpiry", Date.now() + 300000); // 5 minutes
return freshData;
}
Optimisation des composants
// Lazy loading
const LazyTable = React.lazy(() => import('./HeavyTable'));
function DataView() {
const [showTable, setShowTable] = useState(false);
return (
<div>
<button onClick={() => setShowTable(true)}>
Load Data Table
</button>
{showTable && (
<React.Suspense fallback={<div>Loading...</div>}>
<LazyTable data={query1.data} />
</React.Suspense>
)}
</div>
);
}
// Memoization
const expensiveCalculation = useMemo(() => {
return query1.data.reduce((sum, item) => sum + item.value, 0);
}, [query1.data]);
Essais et débogage
Mode de débogage
// Console logging
console.log("Query result:", query1.data);
console.log("Component state:", table1.selectedRow);
console.log("User info:", current_user);
// Error handling
try {
const result = await riskyQuery.trigger();
console.log("Success:", result);
} catch (error) {
console.error("Query failed:", error);
utils.showNotification({
title: "Error",
description: error.message,
notificationType: "error"
});
}
Stratégies d'essai
// Mock data for testing
const mockUsers = [
{ id: 1, name: "John Doe", email: "john@example.com" },
{ id: 2, name: "Jane Smith", email: "jane@example.com" }
];
// Use mock data in development
const userData = retool.environment === 'development'
? mockUsers
: query1.data;
// Test utilities
function runTests() {
const tests = [
{
name: "User creation",
test: () => createUserQuery.data.id > 0
},
{
name: "Email validation",
test: () => /\S+@\S+\.\S+/.test(emailInput.value)
}
];
tests.forEach(({ name, test }) => {
try {
const result = test();
console.log(`✅ ${name}: ${result ? 'PASS' : 'FAIL'}`);
} catch (error) {
console.log(`❌ ${name}: ERROR - ${error.message}`);
}
});
}
Modèles d'intégration
Gestion du Webhook
// Webhook receiver
app.post('/webhook', (req, res) => {
const { event, data } = req.body;
switch (event) {
case 'user.created':
// Trigger user sync
syncUserQuery.trigger({
additionalScope: { userData: data }
});
break;
case 'order.completed':
// Update dashboard
refreshDashboardQuery.trigger();
break;
default:
console.log('Unknown webhook event:', event);
}
res.status(200).json({ received: true });
});
Intégrations de tiers
// Slack integration
async function sendSlackNotification(message) {
return await slackAPI.run({
url: "/chat.postMessage",
method: "POST",
body: {
channel: "#notifications",
text: message,
username: "Retool Bot"
}
});
}
// Google Sheets integration
async function updateSpreadsheet(data) {
return await googleSheetsAPI.run({
url: `/spreadsheets/${sheetId}/values/A1:append`,
method: "POST",
body: {
values: [data],
valueInputOption: "RAW"
}
});
}
Meilleures pratiques
Code Organisation
// Utility functions module
const utils = {
formatCurrency: (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(amount);
},
formatDate: (date) => {
return new Date(date).toLocaleDateString();
},
validateEmail: (email) => {
return /\S+@\S+\.\S+/.test(email);
}
};
// Constants
const CONSTANTS = {
API_ENDPOINTS: {
USERS: '/api/users',
ORDERS: '/api/orders',
PRODUCTS: '/api/products'
},
STATUS_COLORS: {
active: '#10B981',
inactive: '#EF4444',
pending: '#F59E0B'
}
};
Gestion des erreurs
// Global error handler
window.addEventListener('unhandledrejection', (event) => {
console.error('Unhandled promise rejection:', event.reason);
utils.showNotification({
title: "Unexpected Error",
description: "An error occurred. Please try again.",
notificationType: "error"
});
});
// Query error handling
async function safeQueryExecution(query, fallbackData = []) {
try {
const result = await query.trigger();
return result || fallbackData;
} catch (error) {
console.error(`Query ${query.name} failed:`, error);
return fallbackData;
}
}
Pratiques exemplaires en matière de sécurité
// Input sanitization
function sanitizeInput(input) {
return input
.replace(/[<>]/g, '') // Remove HTML tags
.trim()
.substring(0, 1000); // Limit length
}
// SQL injection prevention
// Use parameterized queries instead of string concatenation
// ❌ Bad:
// SELECT * FROM users WHERE id = ${userInput.value}
// ✅ Good:
// SELECT * FROM users WHERE id = {{ userInput.value }}
// XSS prevention
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
Dépannage
Questions communes
// Component not updating
// Solution: Check data binding and refresh triggers
table1.setData(newData);
table1.refresh();
// Query not triggering
// Solution: Check query dependencies and error logs
console.log("Query state:", query1.isFetching);
console.log("Query error:", query1.error);
// Performance issues
// Solution: Optimize queries and use pagination
SELECT * FROM large_table
LIMIT {{ table1.pageSize }}
OFFSET {{ table1.pageIndex * table1.pageSize }};
Outils de débogage
// State inspector
function debugAppState() {
console.log("App State:", {
queries: Object.keys(queries).map(key => ({
name: key,
data: queries[key].data,
isFetching: queries[key].isFetching,
error: queries[key].error
})),
components: Object.keys(components).map(key => ({
name: key,
value: components[key].value,
hidden: components[key].hidden
}))
});
}
// Performance monitoring
const performanceTimer = {
start: (label) => console.time(label),
end: (label) => console.timeEnd(label)
};
performanceTimer.start('query-execution');
await heavyQuery.trigger();
performanceTimer.end('query-execution');
Ressources
Documentation
- Documentation des retouches
- [Référence de l'API JavaScript] (LINK_9)
- [Bibliothèque des composants] (LINK_9)
Communauté
- [Communauté de recyclage] (LINK_9)
- [Serveur de discorde] (LINK_9)
- [Exemples de GitHub] (LINK_9)
Formation
- [Retool University] (LINK_9)
- [Tutoriels vidéo] (LINK_9)
- [Template Gallery] (LINK_9)