Updated Sep 29, 20265 min read
How I use Neovim as a database client
My sqmeow.nvim workflow: connect databases, write SQL in persistent scratchpads, inspect paged results, and review edits without leaving Neovim.
I spend most of my day in Neovim, so opening a separate app just to run a query always felt slow. I used vim-dadbod and nvim-dbee for a while, and learned a lot from both. Then I built my own: sqmeow.nvim.
This post shows how I set it up and use it every day.
#Why sqmeow
- A Rust engine. Queries run outside the editor, and results come back in pages. Paged results keep large queries from blocking the editor.
- Many databases, one workflow. PostgreSQL, MySQL, SQLite, DuckDB, Redis, MongoDB, ScyllaDB, SurrealDB, ClickHouse, Oracle, and Microsoft SQL Server.
- A schema drawer. Browse schemas, tables, views, and columns with their types and keys.
- Edit results in place. Change cells, add or delete rows, and review the staged changes before they're applied.
- Safe by default. It asks before a
DELETEwithoutWHERE, aDROP, or aTRUNCATE, and connections can be read-only.
#Install
You need Neovim 0.10+ and nui.nvim. With lazy.nvim:
return {
"2giosangmitom/sqmeow.nvim",
dependencies = { "MunifTanjim/nui.nvim" },
version = "*",
build = function()
-- Downloads the release binary that matches your platform.
require("sqmeow").install()
end,
opts = {},
cmd = "Sqmeow",
keys = {
{ "<leader>Dd", "<cmd>Sqmeow toggle<cr>", desc = "Toggle" },
{ "<leader>Da", "<cmd>Sqmeow add<cr>", desc = "Add Connection" },
{ "<leader>Ds", "<cmd>Sqmeow scratch<cr>", desc = "New Scratchpad" },
{ "<leader>Dc", "<cmd>Sqmeow cancel<cr>", desc = "Cancel" },
},
}
Run :checkhealth sqmeow to confirm the engine is installed. Prebuilt binaries ship for Linux x86_64 and ARM64, Apple Silicon, and Windows x86_64. Other machines build from source.
#Run your first query
- Run
:Sqmeowto open the drawer and the result window. - Press
Ain the drawer to add a connection, for examplepostgres://postgres:postgres@localhost:5432/app. - Press
<CR>on the connection to connect, thenato create a scratchpad. - Write a query and press
<CR>to run the statement under the cursor. Select lines in visual mode to run only those, or press<leader>Eto run the whole buffer.
Press ? in the drawer or the result window to see every keymap.
#Working with results
A few result-window keys I use constantly:
| Key | Action |
|---|---|
L / H | Next / previous page |
K | Show the row under the cursor |
gf | Filter with a WHERE clause |
= | Filter by the value under the cursor |
s | Sort by the column under the cursor |
x | Export to CSV, JSON, or SQL INSERTs |
For SQL, filters and sorts rerun the query on the database, so they work beyond the visible page. MongoDB uses filter/sort documents; Redis, ScyllaDB, SurrealDB, and closed connections filter cached results in memory. Every result is saved in the query log, and :Sqmeow log reopens it, even after a restart.
#Review changes before applying them
Press i or <CR> to edit a cell, o to stage a new row, or dd to stage a deletion. Use gs to open the review window, inspect the generated SQL, then press <C-s> there to apply it. u undoes the last staged change; U discards them all.
A result needs the table's complete primary or unique key for its plain columns to be editable. Joined results can update each table through its own key; adding rows requires a single-table result.
Press gK in the result window (or K on a table in the drawer) to inspect its columns and indexes.
#Keep passwords out of your config
Connection URLs can read secrets at connect time, so nothing sensitive lands in a dotfile:
export SQMEOW_CONNECTIONS='[{"name": "dev", "url": "postgres://app:{{ env \"PGPASSWORD\" }}@localhost/dev"}]'
Besides env, you can use {{ exec "cmd" }} to ask a password manager, or {{ file "path" }} to read a file. For production databases, tick Read only when you add the connection.
A project can keep its connections in .sqmeow/connections.toml instead, one section per database:
[dev_db]
type = "postgres"
host = "localhost"
port = 5432
database = "my_app_dev"
user = "dev_user"
Project files accept env and file templates but reject exec.
#Complete table and column names
Scratchpads suggest schema, table, and column names through blink.cmp or nvim-cmp. With blink.cmp, register the source:
require("blink.cmp").setup({
sources = {
default = { "lsp", "path", "buffer", "sqmeow" },
providers = {
sqmeow = { name = "Sqmeow", module = "sqmeow.completion.blink" },
},
},
})
Install the sql Treesitter parser for suggestions that follow table aliases into WHERE, GROUP BY, and ORDER BY.
#Bonus: SQL linting and formatting with sqruff
sqruff is a fast SQL linter and formatter written in Rust. I added it to nvim-lspconfig, mason-registry, and conform.nvim, so setup is short.
Install it with :MasonInstall sqruff, then enable the language server. On Neovim 0.11+ with nvim-lspconfig installed:
vim.lsp.enable("sqruff")
For format-on-save through conform.nvim:
return {
"stevearc/conform.nvim",
opts = {
formatters_by_ft = {
sql = { "sqruff" },
},
},
}
Now your scratchpads get diagnostics as you type and formatting on save.