Day 2 of 5
⏱ ~60 minutes
Supabase in 5 Days — Day 2

Database: Tables and Queries

Create tables, define Row Level Security, and query data with the Supabase client.

The Table Editor and SQL

Create tables in the Supabase dashboard Table Editor (GUI) or write SQL in the SQL Editor. Both work — SQL is more powerful for complex schemas.

SQL to create tables
-- In Supabase SQL Editor
CREATE TABLE posts (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  body TEXT,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Enable Row Level Security
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

-- Policy: users can only see their own posts
CREATE POLICY 'Users see own posts' ON posts
  FOR SELECT USING (auth.uid() = user_id);

-- Policy: users can insert their own posts
CREATE POLICY 'Users insert own posts' ON posts
  FOR INSERT WITH CHECK (auth.uid() = user_id);

-- Policy: public posts visible to all
CREATE POLICY 'Public posts readable' ON posts
  FOR SELECT USING (true);  -- or add a published column check
Querying with supabase-js
// SELECT
const { data, error } = await supabase
  .from('posts')
  .select('id, title, created_at')
  .eq('user_id', userId)
  .order('created_at', { ascending: false })
  .limit(10);

// SELECT with JOIN (foreign key)
const { data } = await supabase
  .from('posts')
  .select('*, profiles(name, avatar_url)');

// INSERT
const { data } = await supabase.from('posts').insert({
  title: 'My Post',
  body: 'Content here',
  user_id: user.id
}).select();

// UPDATE
await supabase.from('posts')
  .update({ title: 'New Title' })
  .eq('id', postId);

// DELETE
await supabase.from('posts').delete().eq('id', postId);
📝 Day 2 Exercise
Build a CRUD App with RLS
  1. C
  2. r
  3. e
  4. a
  5. t
  6. e
  7. a
  8. p
  9. o
  10. s
  11. t
  12. s
  13. t
  14. a
  15. b
  16. l
  17. e
  18. .
  19. E
  20. n
  21. a
  22. b
  23. l
  24. e
  25. R
  26. L
  27. S
  28. .
  29. A
  30. d
  31. d
  32. p
  33. o
  34. l
  35. i
  36. c
  37. i
  38. e
  39. s
  40. s
  41. o
  42. u
  43. s
  44. e
  45. r
  46. s
  47. c
  48. a
  49. n
  50. o
  51. n
  52. l
  53. y
  54. s
  55. e
  56. e
  57. a
  58. n
  59. d
  60. e
  61. d
  62. i
  63. t
  64. t
  65. h
  66. e
  67. i
  68. r
  69. o
  70. w
  71. n
  72. p
  73. o
  74. s
  75. t
  76. s
  77. .
  78. B
  79. u
  80. i
  81. l
  82. d
  83. a
  84. R
  85. e
  86. a
  87. c
  88. t
  89. c
  90. o
  91. m
  92. p
  93. o
  94. n
  95. e
  96. n
  97. t
  98. t
  99. h
  100. a
  101. t
  102. l
  103. i
  104. s
  105. t
  106. s
  107. p
  108. o
  109. s
  110. t
  111. s
  112. f
  113. o
  114. r
  115. t
  116. h
  117. e
  118. l
  119. o
  120. g
  121. g
  122. e
  123. d
  124. -
  125. i
  126. n
  127. u
  128. s
  129. e
  130. r
  131. ,
  132. c
  133. r
  134. e
  135. a
  136. t
  137. e
  138. s
  139. n
  140. e
  141. w
  142. p
  143. o
  144. s
  145. t
  146. s
  147. ,
  148. a
  149. n
  150. d
  151. d
  152. e
  153. l
  154. e
  155. t
  156. e
  157. s
  158. t
  159. h
  160. e
  161. m
  162. .

Day 2 Summary

  • Row Level Security (RLS) enforces data access rules at the database level — not in application code.
  • auth.uid() in RLS policies returns the logged-in user's ID. The database checks it automatically.
  • Supabase client queries: .from(table).select().eq().order().limit() chain.
  • Always enable RLS on tables that contain user data. Without it, any authenticated user can read all rows.
Finished this lesson?