I don't know knex in detail and from a quick search, knex currently doesn't support using "limit" on update statements so just a description of the general approach.
First do an update for the row matching the criteria, then select that updated row.
So, first do an update operation that assigns the current user id to the first unprocessed row that either has no user assigned or already has the same user assigned:
update rows
set assignedTo = user.id
where assignedTo=0 or assignedTo=user.id
order by createdAt asc
limit 1
I think it can work like this with knex using a raw query but haven't tried that:
await knex.raw('update rows set assignedTo = :userid where assignedTo=0 or assignedTo= :userid order by createdAt asc limit 1', {userid: user.id})
This will look for the first (earliest createdAt) row that is unassigned or already assigned to the same user and then assign that user. This happens in one go.
You can then search for the row assigned to the user:
const notProcessed = await knex('rows')
.select('*'')
.whereRaw(`status='Not-Processed' and assignedTo=${user.id}`)
.orderByRaw('createdAt asc')
.first();
Notice how now we explicitly look only for a row already assigned to the user.
Combined
await knex.raw('update rows set assignedTo = :userid where assignedTo=0 or assignedTo= :userid order by createdAt asc limit 1', {userid: user.id})
const notProcessed = await knex('rows')
.select('*'')
.whereRaw(`status='Not-Processed' and assignedTo=${user.id}`)
.orderByRaw('createdAt asc')
.first();
Obviously you don't need the select if you don't want to work with the row immediately.
The problem is that when multiple requests are handled at the same time, you have to imagine multiple instances of the code running at the same time in parallel. So, with your original code, two requests could do your select at the same time before any of them does an update. So, both of them will have the same row returned.
By immediately updating the row within the statement, even when two statements run in parallel, the database will make sure they don't see the same row.
An alternative approach for a solution would be to use a mutex (like e.g. async-mutex) around your original code to make sure that your original select and update operation is atomic (happens in one go), but this will most likely increase the response time of your application because in some situations one request handling operation will have to wait on another one to continue.